Adds SSL support to the flagd sync service --------- Signed-off-by: Alexandra Oberaigner <alexandra.oberaigner@dynatrace.com> Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com> Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
This commit is contained in:
parent
9891df2d0c
commit
d50fcc821c
|
|
@ -145,7 +145,7 @@ func buildTransportCredentials(_ context.Context, cfg CollectorConfig) (credenti
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
RootCAs: capool,
|
RootCAs: capool,
|
||||||
MinVersion: tls.VersionTLS13,
|
MinVersion: tls.VersionTLS12,
|
||||||
GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
certs, err := reloader.GetCertificate()
|
certs, err := reloader.GetCertificate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
@ -12,6 +13,7 @@ import (
|
||||||
"github.com/open-feature/flagd/core/pkg/store"
|
"github.com/open-feature/flagd/core/pkg/store"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ISyncService interface {
|
type ISyncService interface {
|
||||||
|
|
@ -28,6 +30,8 @@ type SvcConfigurations struct {
|
||||||
Sources []string
|
Sources []string
|
||||||
Store *store.Flags
|
Store *store.Flags
|
||||||
ContextValues map[string]any
|
ContextValues map[string]any
|
||||||
|
CertPath string
|
||||||
|
KeyPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
|
|
@ -39,6 +43,23 @@ type Service struct {
|
||||||
startupTracker syncTracker
|
startupTracker syncTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadTLSCredentials(certPath string, keyPath string) (credentials.TransportCredentials, error) {
|
||||||
|
// Load server's certificate and private key
|
||||||
|
serverCert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load key pair from certificate paths '%s' and '%s': %w", certPath, keyPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the credentials and return it
|
||||||
|
config := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{serverCert},
|
||||||
|
ClientAuth: tls.NoClientCert,
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials.NewTLS(config), nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewSyncService(cfg SvcConfigurations) (*Service, error) {
|
func NewSyncService(cfg SvcConfigurations) (*Service, error) {
|
||||||
l := cfg.Logger
|
l := cfg.Logger
|
||||||
mux, err := NewMux(cfg.Store, cfg.Sources)
|
mux, err := NewMux(cfg.Store, cfg.Sources)
|
||||||
|
|
@ -46,7 +67,17 @@ func NewSyncService(cfg SvcConfigurations) (*Service, error) {
|
||||||
return nil, fmt.Errorf("error initializing multiplexer: %w", err)
|
return nil, fmt.Errorf("error initializing multiplexer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
server := grpc.NewServer()
|
var server *grpc.Server
|
||||||
|
if cfg.CertPath != "" && cfg.KeyPath != "" {
|
||||||
|
tlsCredentials, err := loadTLSCredentials(cfg.CertPath, cfg.KeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load TLS cert and key: %w", err)
|
||||||
|
}
|
||||||
|
server = grpc.NewServer(grpc.Creds(tlsCredentials))
|
||||||
|
} else {
|
||||||
|
server = grpc.NewServer()
|
||||||
|
}
|
||||||
|
|
||||||
syncv1grpc.RegisterFlagSyncServiceServer(server, &syncHandler{
|
syncv1grpc.RegisterFlagSyncServiceServer(server, &syncHandler{
|
||||||
mux: mux,
|
mux: mux,
|
||||||
log: l,
|
log: l,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package sync
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -14,134 +15,173 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSyncServiceEndToEnd(t *testing.T) {
|
func TestSyncServiceEndToEnd(t *testing.T) {
|
||||||
// given
|
testCases := []struct {
|
||||||
port := 18016
|
certPath string
|
||||||
store, sources := getSimpleFlagStore()
|
keyPath string
|
||||||
|
clientCertPath string
|
||||||
service, err := NewSyncService(SvcConfigurations{
|
tls bool
|
||||||
Logger: logger.NewLogger(nil, false),
|
wantErr bool
|
||||||
Port: uint16(port),
|
}{
|
||||||
Sources: sources,
|
{"./test-cert/server-cert.pem", "./test-cert/server-key.pem", "./test-cert/ca-cert.pem", true, false},
|
||||||
Store: store,
|
{"", "", "", false, false},
|
||||||
})
|
{"./lol/not/a/cert", "./test-cert/server-key.pem", "./test-cert/ca-cert.pem", true, true},
|
||||||
if err != nil {
|
|
||||||
t.Fatal("error creating the service: %w", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
for _, tc := range testCases {
|
||||||
doneChan := make(chan interface{})
|
var testTitle string
|
||||||
|
if tc.tls {
|
||||||
go func() {
|
testTitle = "Testing Sync Service with TLS Connection"
|
||||||
// error ignored, tests will fail if start is not successful
|
} else {
|
||||||
_ = service.Start(ctx)
|
testTitle = "Testing Sync Service without TLS Connection"
|
||||||
close(doneChan)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// trigger manual emits matching sources, so that service can start
|
|
||||||
for _, source := range sources {
|
|
||||||
service.Emit(false, source)
|
|
||||||
}
|
|
||||||
|
|
||||||
// when - derive a client for sync service
|
|
||||||
con, err := grpc.DialContext(ctx, fmt.Sprintf("localhost:%d", port), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(fmt.Printf("error creating grpc dial ctx: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceClient := syncv1grpc.NewFlagSyncServiceClient(con)
|
|
||||||
|
|
||||||
// then
|
|
||||||
|
|
||||||
// sync flags request
|
|
||||||
flags, err := serviceClient.SyncFlags(ctx, &v1.SyncFlagsRequest{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(fmt.Printf("error from sync request: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
syncRsp, err := flags.Recv()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(fmt.Printf("stream error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(syncRsp.GetFlagConfiguration()) == 0 {
|
|
||||||
t.Error("expected non empty sync response, but got empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate emits
|
|
||||||
dataReceived := make(chan interface{})
|
|
||||||
go func() {
|
|
||||||
_, err := flags.Recv()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
t.Run(testTitle, func(t *testing.T) {
|
||||||
|
// given
|
||||||
|
port := 18016
|
||||||
|
store, sources := getSimpleFlagStore()
|
||||||
|
|
||||||
dataReceived <- nil
|
service, err := NewSyncService(SvcConfigurations{
|
||||||
}()
|
Logger: logger.NewLogger(nil, false),
|
||||||
|
Port: uint16(port),
|
||||||
|
Sources: sources,
|
||||||
|
Store: store,
|
||||||
|
CertPath: tc.certPath,
|
||||||
|
KeyPath: tc.keyPath,
|
||||||
|
})
|
||||||
|
|
||||||
// Emit as a resync
|
if tc.wantErr {
|
||||||
service.Emit(true, "A")
|
if err == nil {
|
||||||
|
t.Fatal("expected error creating the service!")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
t.Fatal("unexpected error creating the service: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
case <-dataReceived:
|
doneChan := make(chan interface{})
|
||||||
t.Fatal("expected no data as this is a resync")
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit as a resync
|
go func() {
|
||||||
service.Emit(false, "A")
|
// error ignored, tests will fail if start is not successful
|
||||||
|
_ = service.Start(ctx)
|
||||||
|
close(doneChan)
|
||||||
|
}()
|
||||||
|
|
||||||
select {
|
// trigger manual emits matching sources, so that service can start
|
||||||
case <-dataReceived:
|
for _, source := range sources {
|
||||||
break
|
service.Emit(false, source)
|
||||||
case <-time.After(1 * time.Second):
|
}
|
||||||
t.Fatal("expected data but timeout waiting for sync")
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch all flags
|
// when - derive a client for sync service
|
||||||
allRsp, err := serviceClient.FetchAllFlags(ctx, &v1.FetchAllFlagsRequest{})
|
var con *grpc.ClientConn
|
||||||
if err != nil {
|
if tc.tls {
|
||||||
t.Fatal(fmt.Printf("fetch all error: %v", err))
|
tlsCredentials, e := loadTLSClientCredentials(tc.clientCertPath)
|
||||||
return
|
if e != nil {
|
||||||
}
|
log.Fatal("cannot load TLS credentials: ", e)
|
||||||
|
}
|
||||||
|
con, err = grpc.Dial(fmt.Sprintf("0.0.0.0:%d", port), grpc.WithTransportCredentials(tlsCredentials))
|
||||||
|
} else {
|
||||||
|
con, err = grpc.DialContext(ctx, fmt.Sprintf("localhost:%d", port), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(fmt.Printf("error creating grpc dial ctx: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if allRsp.GetFlagConfiguration() != syncRsp.GetFlagConfiguration() {
|
serviceClient := syncv1grpc.NewFlagSyncServiceClient(con)
|
||||||
t.Errorf("expected both sync and fetch all responses to be same, but got %s from sync & %s from fetch all",
|
|
||||||
syncRsp.GetFlagConfiguration(), allRsp.GetFlagConfiguration())
|
|
||||||
}
|
|
||||||
|
|
||||||
// metadata request
|
// then
|
||||||
metadataRsp, err := serviceClient.GetMetadata(ctx, &v1.GetMetadataRequest{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(fmt.Printf("metadata error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
asMap := metadataRsp.GetMetadata().AsMap()
|
// sync flags request
|
||||||
|
flags, err := serviceClient.SyncFlags(ctx, &v1.SyncFlagsRequest{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(fmt.Printf("error from sync request: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// expect `sources` to be present
|
syncRsp, err := flags.Recv()
|
||||||
if asMap["sources"] == nil {
|
if err != nil {
|
||||||
t.Fatal("expected sources entry in the metadata, but got nil")
|
t.Fatal(fmt.Printf("stream error: %v", err))
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if asMap["sources"] != "A,B,C" {
|
if len(syncRsp.GetFlagConfiguration()) == 0 {
|
||||||
t.Fatal("incorrect sources entry in metadata")
|
t.Error("expected non empty sync response, but got empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate shutdown from context cancellation
|
// validate emits
|
||||||
go func() {
|
dataReceived := make(chan interface{})
|
||||||
cancelFunc()
|
go func() {
|
||||||
}()
|
_, err := flags.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
dataReceived <- nil
|
||||||
case <-doneChan:
|
}()
|
||||||
// exit successful
|
|
||||||
return
|
// Emit as a resync
|
||||||
case <-time.After(2 * time.Second):
|
service.Emit(true, "A")
|
||||||
t.Fatal("service did not exist within sufficient timeframe")
|
|
||||||
|
select {
|
||||||
|
case <-dataReceived:
|
||||||
|
t.Fatal("expected no data as this is a resync")
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit as a resync
|
||||||
|
service.Emit(false, "A")
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-dataReceived:
|
||||||
|
break
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
t.Fatal("expected data but timeout waiting for sync")
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch all flags
|
||||||
|
allRsp, err := serviceClient.FetchAllFlags(ctx, &v1.FetchAllFlagsRequest{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(fmt.Printf("fetch all error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if allRsp.GetFlagConfiguration() != syncRsp.GetFlagConfiguration() {
|
||||||
|
t.Errorf("expected both sync and fetch all responses to be same, but got %s from sync & %s from fetch all",
|
||||||
|
syncRsp.GetFlagConfiguration(), allRsp.GetFlagConfiguration())
|
||||||
|
}
|
||||||
|
|
||||||
|
// metadata request
|
||||||
|
metadataRsp, err := serviceClient.GetMetadata(ctx, &v1.GetMetadataRequest{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(fmt.Printf("metadata error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
asMap := metadataRsp.GetMetadata().AsMap()
|
||||||
|
|
||||||
|
// expect `sources` to be present
|
||||||
|
if asMap["sources"] == nil {
|
||||||
|
t.Fatal("expected sources entry in the metadata, but got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if asMap["sources"] != "A,B,C" {
|
||||||
|
t.Fatal("incorrect sources entry in metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate shutdown from context cancellation
|
||||||
|
go func() {
|
||||||
|
cancelFunc()
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-doneChan:
|
||||||
|
// exit successful
|
||||||
|
return
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("service did not exist within sufficient timeframe")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
9dqUWaknNa6rIUKwvIodkn39C1G7uCpeLw==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
:'
|
||||||
|
This script can be used to recreate the SSL certificates that are used in the sync service test
|
||||||
|
|
||||||
|
Warning: there might be issues running the script on Windows with the -subj argument
|
||||||
|
-> workaround: run the commands manually without the -subj argument and provide info when asked by the console output
|
||||||
|
'
|
||||||
|
rm *.pem
|
||||||
|
|
||||||
|
# 1. Generate CA's private key and self-signed certificate
|
||||||
|
openssl req -x509 -newkey rsa:4096 -days 9999 -nodes -keyout ca-key.pem -out ca-cert.pem -subj "/CN=flagD test certificate"
|
||||||
|
|
||||||
|
# 2. Generate web server's private key and certificate signing request (CSR)
|
||||||
|
openssl req -newkey rsa:4096 -nodes -keyout server-key.pem -out server-req.pem -subj "/CN=flagD test server PR and CSR"
|
||||||
|
|
||||||
|
# 3. Use CA's private key to sign web server's CSR and get back the signed certificate
|
||||||
|
openssl x509 -req -in server-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.cnf
|
||||||
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE6TCCAtGgAwIBAgIUNCaL1+pmzRcfRNGaIgTrD2t3lqswDQYJKoZIhvcNAQEL
|
||||||
|
BQAwITEfMB0GA1UEAwwWZmxhZ0QgdGVzdCBjZXJ0aWZpY2F0ZTAeFw0yNTAxMDgw
|
||||||
|
OTI2MTJaFw0yNTAzMDkwOTI2MTJaMCcxJTAjBgNVBAMMHGZsYWdEIHRlc3Qgc2Vy
|
||||||
|
dmVyIFBSIGFuZCBDU1IwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC3
|
||||||
|
8cZVWtUihF8lQmmKE8ZG896s7KSqPoNDdqrzp0pebwbErHf/iVarUsl9IWuxxCeG
|
||||||
|
Oh65yRQfZH8zp39Yf6oAxgiF+sFITkoWrGwB5HbT6qRwUQC5kLytuY9THTJKcpyo
|
||||||
|
UNTsR46Ne0ix+yM+55nI504tK4U8UdVnQkbnU0tnRlb4LOSA4emDYnCWsl814l1T
|
||||||
|
Np4h1e7Sb8ppwDju07UQq92Kt4oSZIjHQXeJmOt4vko4c8rv3xfUA12cPSajUEw7
|
||||||
|
oiohwd50LKtIm4MyG3jisTvie46xIGHD9AJ0AejuwsSSrAUhZ6S+fVwFVdzPpo9C
|
||||||
|
SKjjrz0fw6FkSeN3Kc61ipYKHf44dsZZB/IQB8CMpLk3cotktu8lwyTDb9URAg0c
|
||||||
|
PpBLZl7PFY4LgMZrIwsUy5CZbcmkzPRqNVfmfKMCy8D/CNV7jhxSj8FxD8zcTn63
|
||||||
|
lrbmMoAIZtDZKBqpzRqVkawxVGPjvn4wXVi/CJC5o04l9bWpXJaGLgS0SVtLT8Iz
|
||||||
|
yWYPXYLadMY7UIn7mqWBRb+x9GHhWGibJLHOD7+oQA+m3/7Sj9fbRHSbKVl8zoRx
|
||||||
|
Fmn8r2104ym+Cgqh54eUg73lc6D1bXAgMbwJu5qbF9rHn3IpKZSI19UB/2RlsxMX
|
||||||
|
py8PrGlKEhS1vwiZDyTe5jEzmgfKzzCe+274JP+ZlQIDAQABoxMwETAPBgNVHREE
|
||||||
|
CDAGhwQAAAAAMA0GCSqGSIb3DQEBCwUAA4ICAQBxCHpqFX3IA+2tE5Sjf+Y3La7q
|
||||||
|
DogiNDsSj5Ngu3wxs76pbUDepSesI4EmpCnaSsjTfChCd24ZXa5seeoK8P8BTHcm
|
||||||
|
yAmWy6G03MvFovnuOlxfBlXhhhCywUbevLPHw1Md9+I8OZP13IYF+agv9CktGulp
|
||||||
|
keYJKcaEvaxS3dLe4GIJC5KVy/hUz47U0yZQbpqlzKkuDQ1RrWoJFh2sSpaPc1Dy
|
||||||
|
/3QwB1IC+WeP6dkDhxikoPHodpPvcrh5S9/HvihE9AUo682wi9MJ0BfhWt6zDJb/
|
||||||
|
WJaYQ0J5whS5PtXhdWdobte6Pz7cInusd2OF1BbR2tc/JBB4VfQWUH1ok3AKQ3kq
|
||||||
|
5yq7+qA/QWeuQiIUmxHtfzfKcfi5QvDgNoNRAp7yHpAXXmYKBfBYF/5JaRon9FHg
|
||||||
|
LaFXEiexoOYZpJkAPbsU3fiSSC+QRRo1imqnLx3L52BUn0/0Z7sLpBL26nRrr4z9
|
||||||
|
3LxgKxpPyBX7/Wv3GPMXPkXP5JVqFs6vMUXrAvg/prLJpvIQWvl4/YiIibmKGONy
|
||||||
|
BK9tYSdV1b/MrTMnzXYgh2/XZ3HaAU7TdQefk3Cs4/sD+l8pRZGvysaxhUoX3Ua2
|
||||||
|
jrOcfhqlr/XbeBWrHosJB5HVbpgGKJ/Dix6PuRqGcRyi3QgzavUyQ81V7MyhtVMI
|
||||||
|
7AWcs98UoJz2+7oLbA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
subjectAltName=IP:0.0.0.0
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIJRQIBADANBgkqhkiG9w0BAQEFAASCCS8wggkrAgEAAoICAQC38cZVWtUihF8l
|
||||||
|
QmmKE8ZG896s7KSqPoNDdqrzp0pebwbErHf/iVarUsl9IWuxxCeGOh65yRQfZH8z
|
||||||
|
p39Yf6oAxgiF+sFITkoWrGwB5HbT6qRwUQC5kLytuY9THTJKcpyoUNTsR46Ne0ix
|
||||||
|
+yM+55nI504tK4U8UdVnQkbnU0tnRlb4LOSA4emDYnCWsl814l1TNp4h1e7Sb8pp
|
||||||
|
wDju07UQq92Kt4oSZIjHQXeJmOt4vko4c8rv3xfUA12cPSajUEw7oiohwd50LKtI
|
||||||
|
m4MyG3jisTvie46xIGHD9AJ0AejuwsSSrAUhZ6S+fVwFVdzPpo9CSKjjrz0fw6Fk
|
||||||
|
SeN3Kc61ipYKHf44dsZZB/IQB8CMpLk3cotktu8lwyTDb9URAg0cPpBLZl7PFY4L
|
||||||
|
gMZrIwsUy5CZbcmkzPRqNVfmfKMCy8D/CNV7jhxSj8FxD8zcTn63lrbmMoAIZtDZ
|
||||||
|
KBqpzRqVkawxVGPjvn4wXVi/CJC5o04l9bWpXJaGLgS0SVtLT8IzyWYPXYLadMY7
|
||||||
|
UIn7mqWBRb+x9GHhWGibJLHOD7+oQA+m3/7Sj9fbRHSbKVl8zoRxFmn8r2104ym+
|
||||||
|
Cgqh54eUg73lc6D1bXAgMbwJu5qbF9rHn3IpKZSI19UB/2RlsxMXpy8PrGlKEhS1
|
||||||
|
vwiZDyTe5jEzmgfKzzCe+274JP+ZlQIDAQABAoICAQCh2ZHazqaUzYZuYWY9wTKI
|
||||||
|
gdIfs8Ubqw+Sj9rRsxQjzWtWKC8Z4H0rGBgECyEYdHEWkRMyA7S5/pJSIAJUG1i5
|
||||||
|
f4ZGZSImfgSAuMv8SksoIeD4lr2dibYK4igzSJBUo04mZ6FCGaBb6utG96PGmMBe
|
||||||
|
3u+RnSaJsbOlPNLofgjt4R1rFw0kPiNaoIZSgrZ10iytqHQxb2zJKuYecK1nr041
|
||||||
|
UhQIF4DcuCsFsBv/LVebkUv7Kh+ZOmJcAW4fqErUDjZVjlWmCFC1RgycQYGJ2FRg
|
||||||
|
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
|
||||||
|
aFIhPf94JnJUQ/J9xwe2MvBIGtpAERp2gw==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
package sync
|
package sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/open-feature/flagd/core/pkg/model"
|
"github.com/open-feature/flagd/core/pkg/model"
|
||||||
"github.com/open-feature/flagd/core/pkg/store"
|
"github.com/open-feature/flagd/core/pkg/store"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getSimpleFlagStore returns a flag store pre-filled with flags from sources A & B & C, which C empty
|
// getSimpleFlagStore returns a flag store pre-filled with flags from sources A & B & C, which C empty
|
||||||
|
|
@ -30,3 +36,24 @@ func getSimpleFlagStore() (*store.Flags, []string) {
|
||||||
|
|
||||||
return flagStore, []string{"A", "B", "C"}
|
return flagStore, []string{"A", "B", "C"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadTLSClientCredentials(certPath string) (credentials.TransportCredentials, error) {
|
||||||
|
// Load certificate of the CA who signed server's certificate
|
||||||
|
pemServerCA, err := os.ReadFile(certPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read file from path '%s'", certPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
if !certPool.AppendCertsFromPEM(pemServerCA) {
|
||||||
|
return nil, fmt.Errorf("failed to add server CA's certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the credentials and return it
|
||||||
|
config := &tls.Config{
|
||||||
|
RootCAs: certPool,
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials.NewTLS(config), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue