Skip to content

Commit fdfa12a

Browse files
authored
Merge pull request #544 from nokia/cluster-client-tls
add configurable TLS cert for the clustering API client
2 parents ee7b896 + 046929f commit fdfa12a

File tree

4 files changed

+98
-58
lines changed

4 files changed

+98
-58
lines changed

docs/user_guide/HA.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,19 @@ clustering:
123123
renew-period: 5s
124124
# debug, enable extra logging messages
125125
debug: false
126+
# tls config for the REST API client
127+
tls:
128+
# string, path to the CA certificate file,
129+
# this will be used to verify the certificates of the gNMIc cluster members
130+
# when `skip-verify` is false
131+
ca-file:
132+
# string, client certificate file.
133+
cert-file:
134+
# string, client key file.
135+
key-file:
136+
# boolean, if true, the client will not verify the server
137+
# certificate against the available certificate chain.
138+
skip-verify: false
126139
```
127140
128141
A `gnmic` instance creates gNMI subscriptions only towards targets for which it acquired locks. It is also responsible for maintaining that lock for the duration of the subscription.

pkg/app/app.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"fmt"
1515
"io"
1616
"log"
17+
"net/http"
1718
"os"
1819
"sort"
1920
"strings"
@@ -76,8 +77,9 @@ type App struct {
7677
targetsLockFn map[string]context.CancelFunc
7778
rootDesc desc.Descriptor
7879
// end collector
79-
router *mux.Router
80-
locker lockers.Locker
80+
router *mux.Router
81+
locker lockers.Locker
82+
clusteringClient *http.Client
8183
// api
8284
apiServices map[string]*lockers.Service
8385
isLeader bool

pkg/app/clustering.go

Lines changed: 68 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"time"
2424

2525
"github.com/openconfig/gnmic/pkg/api/types"
26+
"github.com/openconfig/gnmic/pkg/api/utils"
2627
"github.com/openconfig/gnmic/pkg/lockers"
2728
)
2829

@@ -31,6 +32,7 @@ const (
3132
retryTimer = 10 * time.Second
3233
lockWaitTime = 100 * time.Millisecond
3334
apiServiceName = "gnmic-api"
35+
protocolTagName = "__protocol"
3436
)
3537

3638
var (
@@ -81,9 +83,9 @@ func (a *App) apiServiceRegistration() {
8183
tags = append(tags, fmt.Sprintf("cluster-name=%s", a.Config.Clustering.ClusterName))
8284
tags = append(tags, fmt.Sprintf("instance-name=%s", a.Config.Clustering.InstanceName))
8385
if a.Config.APIServer.TLS != nil {
84-
tags = append(tags, "protocol=https")
86+
tags = append(tags, protocolTagName+"=https")
8587
} else {
86-
tags = append(tags, "protocol=http")
88+
tags = append(tags, protocolTagName+"=http")
8789
}
8890
tags = append(tags, a.Config.Clustering.Tags...)
8991

@@ -538,25 +540,13 @@ func (a *App) getHighestTagsMatches(tagsCount map[string]int) []string {
538540
}
539541

540542
func (a *App) deleteTarget(ctx context.Context, name string) error {
543+
err := a.createAPIClient()
544+
if err != nil {
545+
return err
546+
}
541547
errs := make([]error, 0, len(a.apiServices))
542548
for _, s := range a.apiServices {
543-
scheme := "http"
544-
client := &http.Client{
545-
Timeout: defaultHTTPClientTimeout,
546-
}
547-
for _, t := range s.Tags {
548-
if strings.HasPrefix(t, "protocol=") {
549-
scheme = strings.Split(t, "=")[1]
550-
break
551-
}
552-
}
553-
if scheme == "https" {
554-
client.Transport = &http.Transport{
555-
TLSClientConfig: &tls.Config{
556-
InsecureSkipVerify: true,
557-
},
558-
}
559-
}
549+
scheme := a.getServiceScheme(s)
560550
ctx, cancel := context.WithCancel(ctx)
561551
defer cancel()
562552
url := fmt.Sprintf("%s://%s/api/v1/config/targets/%s", scheme, s.Address, name)
@@ -567,7 +557,7 @@ func (a *App) deleteTarget(ctx context.Context, name string) error {
567557
continue
568558
}
569559

570-
rsp, err := client.Do(req)
560+
rsp, err := a.clusteringClient.Do(req)
571561
if err != nil {
572562
rsp.Body.Close()
573563
a.Logger.Printf("failed deleting target %q: %v", name, err)
@@ -590,29 +580,17 @@ func (a *App) assignTarget(ctx context.Context, tc *types.TargetConfig, service
590580
if err != nil {
591581
return err
592582
}
593-
scheme := "http"
594-
client := &http.Client{
595-
Timeout: defaultHTTPClientTimeout,
596-
}
597-
for _, t := range service.Tags {
598-
if strings.HasPrefix(t, "protocol=") {
599-
scheme = strings.Split(t, "=")[1]
600-
break
601-
}
602-
}
603-
if scheme == "https" {
604-
client.Transport = &http.Transport{
605-
TLSClientConfig: &tls.Config{
606-
InsecureSkipVerify: true,
607-
},
608-
}
583+
err = a.createAPIClient()
584+
if err != nil {
585+
return err
609586
}
587+
scheme := a.getServiceScheme(service)
610588
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s://%s/api/v1/config/targets", scheme, service.Address), buffer)
611589
if err != nil {
612590
return err
613591
}
614592
req.Header.Set("Content-Type", "application/json")
615-
resp, err := client.Do(req)
593+
resp, err := a.clusteringClient.Do(req)
616594
if err != nil {
617595
return err
618596
}
@@ -626,7 +604,7 @@ func (a *App) assignTarget(ctx context.Context, tc *types.TargetConfig, service
626604
if err != nil {
627605
return err
628606
}
629-
resp, err = client.Do(req)
607+
resp, err = a.clusteringClient.Do(req)
630608
if err != nil {
631609
return err
632610
}
@@ -639,27 +617,15 @@ func (a *App) assignTarget(ctx context.Context, tc *types.TargetConfig, service
639617
}
640618

641619
func (a *App) unassignTarget(ctx context.Context, name string, serviceID string) error {
620+
err := a.createAPIClient()
621+
if err != nil {
622+
return err
623+
}
642624
for _, s := range a.apiServices {
643625
if s.ID != serviceID {
644626
continue
645627
}
646-
scheme := "http"
647-
client := &http.Client{
648-
Timeout: defaultHTTPClientTimeout,
649-
}
650-
for _, t := range s.Tags {
651-
if strings.HasPrefix(t, "protocol=") {
652-
scheme = strings.Split(t, "=")[1]
653-
break
654-
}
655-
}
656-
if scheme == "https" {
657-
client.Transport = &http.Transport{
658-
TLSClientConfig: &tls.Config{
659-
InsecureSkipVerify: true,
660-
},
661-
}
662-
}
628+
scheme := a.getServiceScheme(s)
663629
url := fmt.Sprintf("%s://%s/api/v1/targets/%s", scheme, s.Address, name)
664630
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
665631
defer cancel()
@@ -668,7 +634,7 @@ func (a *App) unassignTarget(ctx context.Context, name string, serviceID string)
668634
a.Logger.Printf("failed to create HTTP request: %v", err)
669635
continue
670636
}
671-
rsp, err := client.Do(req)
637+
rsp, err := a.clusteringClient.Do(req)
672638
if err != nil {
673639
// don't close the body here since Body will be nil
674640
a.Logger.Printf("failed HTTP request: %v", err)
@@ -680,3 +646,49 @@ func (a *App) unassignTarget(ctx context.Context, name string, serviceID string)
680646
}
681647
return nil
682648
}
649+
650+
func (a *App) getServiceScheme(service *lockers.Service) string {
651+
scheme := "http"
652+
for _, t := range service.Tags {
653+
if strings.HasPrefix(t, protocolTagName+"=") {
654+
scheme = strings.Split(t, "=")[1]
655+
break
656+
}
657+
}
658+
return scheme
659+
}
660+
661+
func (a *App) createAPIClient() error {
662+
if a.clusteringClient != nil {
663+
return nil
664+
}
665+
// no certs
666+
if a.Config.Clustering.TLS == nil {
667+
a.clusteringClient = &http.Client{
668+
Timeout: defaultHTTPClientTimeout,
669+
Transport: &http.Transport{
670+
TLSClientConfig: &tls.Config{
671+
InsecureSkipVerify: true,
672+
},
673+
},
674+
}
675+
return nil
676+
}
677+
// with certs
678+
tlsConfig, err := utils.NewTLSConfig(
679+
a.Config.Clustering.TLS.CaFile,
680+
a.Config.Clustering.TLS.CertFile,
681+
a.Config.Clustering.TLS.KeyFile, "",
682+
a.Config.Clustering.TLS.SkipVerify,
683+
false)
684+
if err != nil {
685+
return err
686+
}
687+
a.clusteringClient = &http.Client{
688+
Timeout: defaultHTTPClientTimeout,
689+
Transport: &http.Transport{
690+
TLSClientConfig: tlsConfig,
691+
},
692+
}
693+
return nil
694+
}

pkg/config/clustering.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
package config
1010

1111
import (
12+
"fmt"
1213
"os"
1314
"time"
1415

1516
"github.com/google/uuid"
17+
"github.com/openconfig/gnmic/pkg/api/types"
1618
)
1719

1820
const (
@@ -32,6 +34,7 @@ type clustering struct {
3234
LeaderWaitTimer time.Duration `mapstructure:"leader-wait-timer,omitempty" json:"leader-wait-timer,omitempty" yaml:"leader-wait-timer,omitempty"`
3335
Tags []string `mapstructure:"tags,omitempty" json:"tags,omitempty" yaml:"tags,omitempty"`
3436
Locker map[string]interface{} `mapstructure:"locker,omitempty" json:"locker,omitempty" yaml:"locker,omitempty"`
37+
TLS *types.TLSConfig `mapstructure:"tls,omitempty" json:"tls,omitempty" yaml:"tls,omitempty"`
3538
}
3639

3740
func (c *Config) GetClustering() error {
@@ -50,6 +53,16 @@ func (c *Config) GetClustering() error {
5053
for i := range c.Clustering.Tags {
5154
c.Clustering.Tags[i] = os.ExpandEnv(c.Clustering.Tags[i])
5255
}
56+
if c.FileConfig.IsSet("clustering/tls") {
57+
c.Clustering.TLS = new(types.TLSConfig)
58+
c.Clustering.TLS.CaFile = os.ExpandEnv(c.FileConfig.GetString("clustering/tls/ca-file"))
59+
c.Clustering.TLS.CertFile = os.ExpandEnv(c.FileConfig.GetString("clustering/tls/cert-file"))
60+
c.Clustering.TLS.KeyFile = os.ExpandEnv(c.FileConfig.GetString("clustering/tls/key-file"))
61+
c.Clustering.TLS.SkipVerify = os.ExpandEnv(c.FileConfig.GetString("clustering/tls/skip-verify")) == trueString
62+
if err := c.APIServer.TLS.Validate(); err != nil {
63+
return fmt.Errorf("clustering TLS config error: %w", err)
64+
}
65+
}
5366
c.setClusteringDefaults()
5467
return c.getLocker()
5568
}

0 commit comments

Comments
 (0)