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{
|
||||
RootCAs: capool,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
certs, err := reloader.GetCertificate()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package sync
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
|
|
@ -12,6 +13,7 @@ import (
|
|||
"github.com/open-feature/flagd/core/pkg/store"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
type ISyncService interface {
|
||||
|
|
@ -28,6 +30,8 @@ type SvcConfigurations struct {
|
|||
Sources []string
|
||||
Store *store.Flags
|
||||
ContextValues map[string]any
|
||||
CertPath string
|
||||
KeyPath string
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
|
|
@ -39,6 +43,23 @@ type Service struct {
|
|||
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) {
|
||||
l := cfg.Logger
|
||||
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)
|
||||
}
|
||||
|
||||
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{
|
||||
mux: mux,
|
||||
log: l,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package sync
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -14,134 +15,173 @@ import (
|
|||
)
|
||||
|
||||
func TestSyncServiceEndToEnd(t *testing.T) {
|
||||
// given
|
||||
port := 18016
|
||||
store, sources := getSimpleFlagStore()
|
||||
|
||||
service, err := NewSyncService(SvcConfigurations{
|
||||
Logger: logger.NewLogger(nil, false),
|
||||
Port: uint16(port),
|
||||
Sources: sources,
|
||||
Store: store,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating the service: %w", err)
|
||||
return
|
||||
testCases := []struct {
|
||||
certPath string
|
||||
keyPath string
|
||||
clientCertPath string
|
||||
tls bool
|
||||
wantErr bool
|
||||
}{
|
||||
{"./test-cert/server-cert.pem", "./test-cert/server-key.pem", "./test-cert/ca-cert.pem", true, false},
|
||||
{"", "", "", false, false},
|
||||
{"./lol/not/a/cert", "./test-cert/server-key.pem", "./test-cert/ca-cert.pem", true, true},
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
doneChan := make(chan interface{})
|
||||
|
||||
go func() {
|
||||
// error ignored, tests will fail if start is not successful
|
||||
_ = service.Start(ctx)
|
||||
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
|
||||
for _, tc := range testCases {
|
||||
var testTitle string
|
||||
if tc.tls {
|
||||
testTitle = "Testing Sync Service with TLS Connection"
|
||||
} else {
|
||||
testTitle = "Testing Sync Service without TLS Connection"
|
||||
}
|
||||
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
|
||||
service.Emit(true, "A")
|
||||
if tc.wantErr {
|
||||
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 {
|
||||
case <-dataReceived:
|
||||
t.Fatal("expected no data as this is a resync")
|
||||
case <-time.After(1 * time.Second):
|
||||
break
|
||||
}
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
doneChan := make(chan interface{})
|
||||
|
||||
// Emit as a resync
|
||||
service.Emit(false, "A")
|
||||
go func() {
|
||||
// error ignored, tests will fail if start is not successful
|
||||
_ = service.Start(ctx)
|
||||
close(doneChan)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-dataReceived:
|
||||
break
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("expected data but timeout waiting for sync")
|
||||
}
|
||||
// trigger manual emits matching sources, so that service can start
|
||||
for _, source := range sources {
|
||||
service.Emit(false, source)
|
||||
}
|
||||
|
||||
// fetch all flags
|
||||
allRsp, err := serviceClient.FetchAllFlags(ctx, &v1.FetchAllFlagsRequest{})
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Printf("fetch all error: %v", err))
|
||||
return
|
||||
}
|
||||
// when - derive a client for sync service
|
||||
var con *grpc.ClientConn
|
||||
if tc.tls {
|
||||
tlsCredentials, e := loadTLSClientCredentials(tc.clientCertPath)
|
||||
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() {
|
||||
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())
|
||||
}
|
||||
serviceClient := syncv1grpc.NewFlagSyncServiceClient(con)
|
||||
|
||||
// metadata request
|
||||
metadataRsp, err := serviceClient.GetMetadata(ctx, &v1.GetMetadataRequest{})
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Printf("metadata error: %v", err))
|
||||
return
|
||||
}
|
||||
// then
|
||||
|
||||
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
|
||||
if asMap["sources"] == nil {
|
||||
t.Fatal("expected sources entry in the metadata, but got nil")
|
||||
}
|
||||
syncRsp, err := flags.Recv()
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Printf("stream error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if asMap["sources"] != "A,B,C" {
|
||||
t.Fatal("incorrect sources entry in metadata")
|
||||
}
|
||||
if len(syncRsp.GetFlagConfiguration()) == 0 {
|
||||
t.Error("expected non empty sync response, but got empty")
|
||||
}
|
||||
|
||||
// validate shutdown from context cancellation
|
||||
go func() {
|
||||
cancelFunc()
|
||||
}()
|
||||
// validate emits
|
||||
dataReceived := make(chan interface{})
|
||||
go func() {
|
||||
_, err := flags.Recv()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-doneChan:
|
||||
// exit successful
|
||||
return
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("service did not exist within sufficient timeframe")
|
||||
dataReceived <- nil
|
||||
}()
|
||||
|
||||
// Emit as a resync
|
||||
service.Emit(true, "A")
|
||||
|
||||
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-----
|
||||
MIIFJTCCAw2gAwIBAgIUHkLmoT3U1jDYbXAqX84fjR/Qw5kwDQYJKoZIhvcNAQEL
|
||||
BQAwITEfMB0GA1UEAwwWZmxhZ0QgdGVzdCBjZXJ0aWZpY2F0ZTAgFw0yNTAxMDgw
|
||||
OTI0MThaGA8yMDUyMDUyNTA5MjQxOFowITEfMB0GA1UEAwwWZmxhZ0QgdGVzdCBj
|
||||
ZXJ0aWZpY2F0ZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALQK8r5J
|
||||
7wOvdMsbGy63wlOie97lMHT/HSfneyTEnvRM4ZenKNjrKI6zlyYMQX9/RQs9qCxF
|
||||
QQDFMhLoMrQKkDptVur+PhYCa/uAWUtTFVXJMbJ2Zdzbg86WCgOlRLwMqGiwnu0l
|
||||
25dk5Ja5nE4HSSg5SipLxtCCdxT3n5YmQBO+Kkyc1Uxgk6yQMQcdQENGhQHazVzy
|
||||
fAsrutJFZNtkYXObR9t6/MyYgg3zFjNRCpOYhc9misT54TVteb9L0smLqMhVfQkh
|
||||
CFWkVEWmaeGyTYAR7gGvFy9b7N/45FfBvlumgR7KiG/uLJNadCQtf7v3pRN36SBH
|
||||
aZleAp8KWmy6N2IMuWx/hMQCVnzoi05gvYkCWJSoseobhiwaXsDFQCc8ZB/c2H9C
|
||||
yMmyKRL+c3RM7lI/InxLmpS94xJIhDuvDiEPYTWqY3BPqCvAly8LXEYBqgJNsnOa
|
||||
+pdoJQ/rl87pIdDt3CK/wWQ1GIlp1v7aYY53riM4i0Hvnk6SPNBgpUXbqAZjpnOb
|
||||
fsIrKGO/FAJ5Hc3mfiuTj15jDoxPPUPqzj4yP2h7WqbdYwEpDvTd0Cd+tjgCuQUj
|
||||
JU2MpTcNUQQQFdXASZQgfXHOTW873zIx4mourcO7jkEDwId13H9QMU4JPG9ZsLG6
|
||||
p08S26yx8G0chV/Q7BNVfOTCKAc8GH+94CynAgMBAAGjUzBRMB0GA1UdDgQWBBQs
|
||||
ubjWSGXffy7mMQGV2EdbdUwtPjAfBgNVHSMEGDAWgBQsubjWSGXffy7mMQGV2Edb
|
||||
dUwtPjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBwJyNT3ldS
|
||||
DsvOOFH5VT+yMByXv690FH7aYBu5VeSpd8oZPJZgzRh58fTzUxrnPk7iyuQzlgCw
|
||||
ZZssvObDEA1eK9vG1svomY8yu8VrpKG/yl0YItnZpuQTvCfJsNOm1Qe0j8FPJVOu
|
||||
Jgges3i+QV18o2dmtQKTTWdExu2J30eQgL8SB7hG9vE6tfcVkzCw6+5ev+fyO9kd
|
||||
DxrN9YY/FKjNMY2WSCjbQ99NFNS3Qf0MFLLy+V3oiYlT1/qkuX9omLIL6qDabuXE
|
||||
8XbppIG+ku610hiVRd4ZZyTH1EIThqevs7y3zQYFQNDL8wPfYHRTl00Fto/NLcTH
|
||||
+cAhf5nC5R4PqvSJZ1IZkxUj/8oljUtMsbFKTekgJGnP/VzDFoNmYO6lW3rBFM1L
|
||||
ovFJhaesnAKQ6WFk+Yru9kFylX2dHqFsB0SlggGS+r8kwbrXlXJhWAE/WpPVyU+H
|
||||
IUyYluNLKKvMBYa9GqUC6C7XlZmiuemZy0oCy4gHqdmII09Bj3C3nT7+Ms4D21jh
|
||||
fZKN4WZcpsA2XaE27XsAqJp2gIV+SL3VqD3hAOFIRWAwQVvTlVJ1GWM1DJBVnLpp
|
||||
BFK4mO0Mga0AbzJ6VL9ElvXVjqwRl8gbeT7yHWQ8A0r7yl4n0i4FloDeoCmr7i9O
|
||||
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
|
||||
mvQHTxJ51fVQFucFrhyH4UZXjBajku+JUQJkC23UJEkPWKGKXUnaJide+Ai2dEnV
|
||||
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
|
||||
aFIhPf94JnJUQ/J9xwe2MvBIGtpAERp2gw==
|
||||
-----END PRIVATE KEY-----
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/open-feature/flagd/core/pkg/model"
|
||||
"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
|
||||
|
|
@ -30,3 +36,24 @@ func getSimpleFlagStore() (*store.Flags, []string) {
|
|||
|
||||
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