Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions core/pkg/runtime/from_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
msync "sync"
"time"

"github.com/open-feature/flagd/core/pkg/sync/grpc/credentials"

"github.com/open-feature/flagd/core/pkg/service"

"go.opentelemetry.io/otel/exporters/prometheus"
Expand All @@ -21,7 +23,6 @@ import (
"github.com/open-feature/flagd/core/pkg/sync"
"github.com/open-feature/flagd/core/pkg/sync/file"
"github.com/open-feature/flagd/core/pkg/sync/grpc"
"github.com/open-feature/flagd/core/pkg/sync/grpc/credentials"
httpSync "github.com/open-feature/flagd/core/pkg/sync/http"
"github.com/open-feature/flagd/core/pkg/sync/kubernetes"
"github.com/robfig/cron"
Expand Down Expand Up @@ -53,6 +54,7 @@ type SourceConfig struct {

BearerToken string `json:"bearerToken,omitempty"`
CertPath string `json:"certPath,omitempty"`
TLS bool `json:"tls,omitempty"`
ProviderID string `json:"providerID,omitempty"`
Selector string `json:"selector,omitempty"`
}
Expand Down Expand Up @@ -162,10 +164,11 @@ func NewGRPC(config SourceConfig, logger *logger.Logger) *grpc.Sync {
zap.String("component", "sync"),
zap.String("sync", "grpc"),
),
CredentialBuilder: &credentials.CredentialBuilder{},
CertPath: config.CertPath,
ProviderID: config.ProviderID,
Secure: config.TLS,
Selector: config.Selector,
CredentialBuilder: &credentials.CredentialBuilder{},
}
}

Expand Down Expand Up @@ -218,24 +221,19 @@ func ParseSources(sourcesFlag string) ([]SourceConfig, error) {
if err := json.Unmarshal([]byte(sourcesFlag), &syncProvidersParsed); err != nil {
return syncProvidersParsed, fmt.Errorf("unable to parse sync providers: %w", err)
}
for i, sp := range syncProvidersParsed {
for _, sp := range syncProvidersParsed {
if sp.URI == "" {
return syncProvidersParsed, errors.New("sync provider argument parse: uri is a required field")
}
if sp.Provider == "" {
return syncProvidersParsed, errors.New("sync provider argument parse: provider is a required field")
}
switch uriB := []byte(sp.URI); {
Comment thread
james-milligan marked this conversation as resolved.
case regFile.Match(uriB):
syncProvidersParsed[i].URI = regFile.ReplaceAllString(syncProvidersParsed[i].URI, "")
case regCrd.Match(uriB):
syncProvidersParsed[i].URI = regCrd.ReplaceAllString(syncProvidersParsed[i].URI, "")
}
}
return syncProvidersParsed, nil
}

// ParseSyncProviderURIs uri flag based sync sources to SourceConfig array. Replaces uri prefixes where necessary
// ParseSyncProviderURIs uri flag based sync sources to SourceConfig array. Replaces uri prefixes where necessary to
// derive SourceConfig
func ParseSyncProviderURIs(uris []string) ([]SourceConfig, error) {
syncProvidersParsed := []SourceConfig{}

Expand All @@ -256,10 +254,16 @@ func ParseSyncProviderURIs(uris []string) ([]SourceConfig, error) {
URI: uri,
Provider: syncProviderHTTP,
})
case regGRPC.Match(uriB), regGRPCSecure.Match(uriB):
case regGRPC.Match(uriB):
syncProvidersParsed = append(syncProvidersParsed, SourceConfig{
URI: uri,
URI: regGRPC.ReplaceAllString(uri, ""),
Provider: syncProviderGrpc,
})
case regGRPCSecure.Match(uriB):
syncProvidersParsed = append(syncProvidersParsed, SourceConfig{
URI: regGRPCSecure.ReplaceAllString(uri, ""),
Provider: syncProviderGrpc,
TLS: true,
})
default:
return syncProvidersParsed, fmt.Errorf("invalid sync uri argument: %s, must start with 'file:', "+
Expand Down
64 changes: 39 additions & 25 deletions core/pkg/runtime/from_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestParseSource(t *testing.T) {
out: []SourceConfig{
{
URI: "config/samples/example_flags.json",
Provider: "file",
Provider: syncProviderFile,
},
},
},
Expand All @@ -34,55 +34,62 @@ func TestParseSource(t *testing.T) {
out: []SourceConfig{
{
URI: "config/samples/example_flags.json",
Provider: "file",
Provider: syncProviderFile,
},
{
URI: "http://test.com",
Provider: "http",
Provider: syncProviderHTTP,
BearerToken: ":)",
},
Comment thread
Kavindu-Dodan marked this conversation as resolved.
{
URI: "host:port",
Provider: "grpc",
Provider: syncProviderGrpc,
},
{
URI: "default/my-crd",
Provider: "kubernetes",
Provider: syncProviderKubernetes,
},
},
},
"multiple-syncs-with-options": {
in: `[
{"uri":"file:config/samples/example_flags.json","provider":"file"},
{"uri":"https://test.com","provider":"http","bearerToken":":)"},
{"uri":"host:port","provider":"grpc"},
{"uri":"host:port","provider":"grpcs","providerID":"appA","selector":"source=database"},
{"uri":"core.openfeature.dev/namespace/my-crd","provider":"kubernetes"}
]`,
in: `[{"uri":"config/samples/example_flags.json","provider":"file"},
{"uri":"http://my-flag-source.json","provider":"http","bearerToken":"bearer-dji34ld2l"},
{"uri":"https://secure-remote","provider":"http","bearerToken":"bearer-dji34ld2l"},
{"uri":"default/my-flag-config","provider":"kubernetes"},
{"uri":"grpc-source:8080","provider":"grpc"},
{"uri":"my-flag-source:8080","provider":"grpc", "tls":true, "certPath": "/certs/ca.cert", "providerID": "flagd-weatherapp-sidecar", "selector": "source=database,app=weatherapp"}]
`,
expectErr: false,
out: []SourceConfig{
{
URI: "config/samples/example_flags.json",
Provider: "file",
Provider: syncProviderFile,
},
{
URI: "https://test.com",
Provider: "http",
BearerToken: ":)",
URI: "http://my-flag-source.json",
Provider: syncProviderHTTP,
BearerToken: "bearer-dji34ld2l",
},
{
URI: "host:port",
Provider: "grpc",
URI: "https://secure-remote",
Provider: syncProviderHTTP,
BearerToken: "bearer-dji34ld2l",
},
{
URI: "host:port",
Provider: "grpcs",
ProviderID: "appA",
Selector: "source=database",
URI: "default/my-flag-config",
Provider: syncProviderKubernetes,
},
{
URI: "namespace/my-crd",
Provider: "kubernetes",
URI: "grpc-source:8080",
Provider: syncProviderGrpc,
},
{
URI: "my-flag-source:8080",
Provider: syncProviderGrpc,
TLS: true,
CertPath: "/certs/ca.cert",
ProviderID: "flagd-weatherapp-sidecar",
Selector: "source=database,app=weatherapp",
},
},
},
Expand Down Expand Up @@ -138,6 +145,7 @@ func TestParseSyncProviderURIs(t *testing.T) {
"file:my-file.json",
"https://test.com",
"grpc://host:port",
"grpcs://secure-grpc",
"core.openfeature.dev/default/my-crd",
},
expectErr: false,
Expand All @@ -151,8 +159,14 @@ func TestParseSyncProviderURIs(t *testing.T) {
Provider: "http",
},
{
URI: "grpc://host:port",
URI: "host:port",
Provider: "grpc",
TLS: false,
},
{
URI: "secure-grpc",
Provider: "grpc",
TLS: true,
},
{
URI: "default/my-crd",
Expand Down
26 changes: 10 additions & 16 deletions core/pkg/sync/grpc/credentials/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,29 @@ import (
"crypto/x509"
"fmt"
"os"
"strings"

"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)

const (
// Prefix for GRPC URL inputs. GRPC does not define a standard prefix. This prefix helps to differentiate remote
// URLs for REST APIs (i.e - HTTP) from GRPC endpoints.
Prefix = "grpc://"
PrefixSecure = "grpcs://"

tlsVersion = tls.VersionTLS12
)
const tlsVersion = tls.VersionTLS12

type Builder interface {
Build(string, string) (credentials.TransportCredentials, error)
Build(secure bool, certPath string) (credentials.TransportCredentials, error)
}

type CredentialBuilder struct{}

// Build is a helper to build grpc credentials.TransportCredentials based on source and cert path
func (cb *CredentialBuilder) Build(source string, certPath string) (credentials.TransportCredentials, error) {
if strings.Contains(source, Prefix) {
return insecure.NewCredentials(), nil
}
func (cb *CredentialBuilder) Build(secure bool, certPath string) (credentials.TransportCredentials, error) {
if !secure {
// check if certificate is set & make this an error so that we do not establish an unwanted insecure connection
if certPath != "" {
return nil, fmt.Errorf("provided a non empty certificate %s, but requested an insecure connection."+
" Please check configurations of the grpc sync source", certPath)
}

if !strings.Contains(source, PrefixSecure) {
return nil, fmt.Errorf("invalid source. grpc source must contain prefix %s or %s", Prefix, PrefixSecure)
return insecure.NewCredentials(), nil
}

if certPath == "" {
Expand Down
21 changes: 11 additions & 10 deletions core/pkg/sync/grpc/credentials/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,51 +64,52 @@ func TestCredentialBuilder_Build(t *testing.T) {

tests := []struct {
name string
source string
certPath string
secure bool
expectSecProto string
error bool
}{
{
name: "Insecure source results in insecure connection",
source: Prefix + "some.domain",
secure: false,
certPath: "",
expectSecProto: insecure,
},
{
name: "Secure source results in secure connection",
source: PrefixSecure + "some.domain",
certPath: validCertFile,
secure: true,
expectSecProto: tls,
},
{
name: "Secure source with no certificate results in a secure connection",
source: PrefixSecure + "some.domain",
secure: true,
expectSecProto: tls,
},
{
name: "Invalid cert path results in an error",
source: PrefixSecure + "some.domain",
secure: true,
certPath: "invalid/path",
error: true,
},
{
name: "Invalid certificate results in an error",
source: PrefixSecure + "some.domain",
secure: true,
certPath: invalidCertFile,
error: true,
},
{
name: "Invalid prefix results in an error",
source: "http://some.domain",
error: true,
name: "Prevent insecure if certificate path is set - configuration check",
secure: false,
certPath: validCertFile,
error: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
builder := CredentialBuilder{}
tCred, err := builder.Build(test.source, test.certPath)
tCred, err := builder.Build(test.secure, test.certPath)

if test.error {
if err == nil {
Expand Down
10 changes: 5 additions & 5 deletions core/pkg/sync/grpc/credentials/mock/builder.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading