From d73754cb2181ae3746b3d14fd21eb3aed0a8ab70 Mon Sep 17 00:00:00 2001 From: Toan Nguyen Date: Sat, 7 Sep 2024 00:09:27 +0700 Subject: [PATCH 1/2] feat: tls config for server --- schema/setting.go | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/schema/setting.go b/schema/setting.go index c757774..90f4634 100644 --- a/schema/setting.go +++ b/schema/setting.go @@ -101,16 +101,49 @@ func (rs RetryPolicySetting) Validate() error { return nil } +// TLSConfig represents the transport layer security (LTS) configuration for the HTTP client +type TLSConfig struct { + // Path to the TLS cert to use for TLS required connections. + CertFile string `json:"certFile,omitempty" yaml:"certFile,omitempty" mapstructure:"certFile"` + // Alternative to cert_file. Provide the certificate contents as a string instead of a filepath. + CertPem string `json:"certPem,omitempty" yaml:"certPem,omitempty" mapstructure:"certPem"` + // Path to the TLS key to use for TLS required connections. + KeyFile string `json:"keyFile,omitempty" yaml:"keyFile,omitempty" mapstructure:"keyFile"` + // Alternative to key_file. Provide the key contents as a string instead of a filepath. + KeyPem string `json:"keyPem,omitempty" yaml:"keyPem,omitempty" mapstructure:"keyPem"` + // Path to the CA cert. For a client this verifies the server certificate. For a server this verifies client certificates. + // If empty uses system root CA. + CAFile string `json:"caFile,omitempty" yaml:"caFile,omitempty" mapstructure:"caFile"` + // Alternative to ca_file. Provide the CA cert contents as a string instead of a filepath. + CAPem string `json:"caPem,omitempty" yaml:"caPem,omitempty" mapstructure:"caPem"` + // Additionally you can configure TLS to be enabled but skip verifying the server's certificate chain. + InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" mapstructure:"insecureSkipVerify"` + // Whether to load the system certificate authorities pool alongside the certificate authority. + IncludeSystemCACertsPool bool `json:"includeSystemCACertsPool,omitempty" yaml:"includeSystemCACertsPool,omitempty" mapstructure:"includeSystemCACertsPool"` + // Minimum acceptable TLS version. + MinVersion string `json:"minVersion,omitempty" yaml:"minVersion,omitempty" mapstructure:"minVersion"` + // Maximum acceptable TLS version. + MaxVersion string `json:"maxVersion,omitempty" yaml:"maxVersion,omitempty" mapstructure:"maxVersion"` + // Explicit cipher suites can be set. If left blank, a safe default list is used. + // See https://go.dev/src/crypto/tls/cipher_suites.go for a list of supported cipher suites. + CipherSuites []string `json:"cipherSuites,omitempty" yaml:"cipherSuites,omitempty" mapstructure:"cipherSuites"` + // Specifies the duration after which the certificate will be reloaded. If not set, it will never be reloaded. + // The interval unit is minute + ReloadInterval uint `json:"reloadInterval,omitempty" yaml:"reloadInterval,omitempty" mapstructure:"reloadInterval"` +} + // ServerConfig contains server configurations type ServerConfig struct { URL EnvString `json:"url" yaml:"url" mapstructure:"url"` - ID string `json:"id,omitempty" yaml:"id,omitempty" mapstructure:"group"` + ID string `json:"id,omitempty" yaml:"id,omitempty" mapstructure:"id"` Headers map[string]EnvString `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers"` // configure the request timeout in seconds, default 30s Timeout *EnvInt `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout"` Retry *RetryPolicySetting `json:"retry,omitempty" yaml:"retry,omitempty" mapstructure:"retry"` SecuritySchemes map[string]SecurityScheme `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty" mapstructure:"securitySchemes"` Security AuthSecurities `json:"security,omitempty" yaml:"security,omitempty" mapstructure:"security"` + // The transport layer security (LTS) configuration for the HTTP client to communicate the current server + TLS *TLSConfig `json:"tls,omitempty" yaml:"tls,omitempty" mapstructure:"tls"` } // Validate if the current instance is valid From c36324001ab45f4ca18c750a1ca54f97dd0c8eca Mon Sep 17 00:00:00 2001 From: Toan Nguyen Date: Wed, 9 Oct 2024 10:51:40 +0700 Subject: [PATCH 2/2] move tls config to server object --- jsonschema/ndc-rest-schema.jsonschema | 81 ++++++ schema/auth.go | 10 +- schema/env.go | 354 ++++++++++++++++++++++---- schema/env_test.go | 139 ++++++++++ schema/setting.go | 70 ++--- 5 files changed, 567 insertions(+), 87 deletions(-) diff --git a/jsonschema/ndc-rest-schema.jsonschema b/jsonschema/ndc-rest-schema.jsonschema index 1887e85..98628f0 100644 --- a/jsonschema/ndc-rest-schema.jsonschema +++ b/jsonschema/ndc-rest-schema.jsonschema @@ -132,6 +132,16 @@ "type": "object", "description": "EncodingObject represents the Encoding Object that contains serialization strategy for application/x-www-form-urlencoded\n\n[Encoding Object]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#encoding-object" }, + "EnvBoolean": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, "EnvInt": { "oneOf": [ { @@ -158,6 +168,19 @@ "EnvString": { "type": "string" }, + "EnvStrings": { + "oneOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, "ForeignKeyConstraint": { "properties": { "column_mapping": { @@ -750,6 +773,9 @@ }, "security": { "$ref": "#/$defs/AuthSecurities" + }, + "tls": { + "$ref": "#/$defs/TLSConfig" } }, "additionalProperties": false, @@ -759,6 +785,61 @@ ], "description": "ServerConfig contains server configurations" }, + "TLSConfig": { + "properties": { + "certFile": { + "$ref": "#/$defs/EnvString", + "description": "Path to the TLS cert to use for TLS required connections." + }, + "certPem": { + "$ref": "#/$defs/EnvString", + "description": "Alternative to cert_file. Provide the certificate contents as a string instead of a filepath." + }, + "keyFile": { + "$ref": "#/$defs/EnvString", + "description": "Path to the TLS key to use for TLS required connections." + }, + "keyPem": { + "$ref": "#/$defs/EnvString", + "description": "Alternative to key_file. Provide the key contents as a string instead of a filepath." + }, + "caFile": { + "$ref": "#/$defs/EnvString", + "description": "Path to the CA cert. For a client this verifies the server certificate. For a server this verifies client certificates.\nIf empty uses system root CA." + }, + "caPem": { + "$ref": "#/$defs/EnvString", + "description": "Alternative to ca_file. Provide the CA cert contents as a string instead of a filepath." + }, + "insecureSkipVerify": { + "$ref": "#/$defs/EnvBoolean", + "description": "Additionally you can configure TLS to be enabled but skip verifying the server's certificate chain." + }, + "includeSystemCACertsPool": { + "$ref": "#/$defs/EnvBoolean", + "description": "Whether to load the system certificate authorities pool alongside the certificate authority." + }, + "minVersion": { + "$ref": "#/$defs/EnvString", + "description": "Minimum acceptable TLS version." + }, + "maxVersion": { + "$ref": "#/$defs/EnvString", + "description": "Maximum acceptable TLS version." + }, + "cipherSuites": { + "$ref": "#/$defs/EnvStrings", + "description": "Explicit cipher suites can be set. If left blank, a safe default list is used.\nSee https://go.dev/src/crypto/tls/cipher_suites.go for a list of supported cipher suites." + }, + "reloadInterval": { + "$ref": "#/$defs/EnvInt", + "description": "Specifies the duration after which the certificate will be reloaded. If not set, it will never be reloaded.\nThe interval unit is minute" + } + }, + "additionalProperties": false, + "type": "object", + "description": "TLSConfig represents the transport layer security (LTS) configuration for the mutualTLS authentication" + }, "Type": { "type": "object" }, diff --git a/schema/auth.go b/schema/auth.go index 7cfaf02..398c80c 100644 --- a/schema/auth.go +++ b/schema/auth.go @@ -18,6 +18,7 @@ const ( HTTPAuthScheme SecuritySchemeType = "http" OAuth2Scheme SecuritySchemeType = "oauth2" OpenIDConnectScheme SecuritySchemeType = "openIdConnect" + MutualTLSScheme SecuritySchemeType = "mutualTLS" ) var securityScheme_enums = []SecuritySchemeType{ @@ -25,6 +26,7 @@ var securityScheme_enums = []SecuritySchemeType{ HTTPAuthScheme, OAuth2Scheme, OpenIDConnectScheme, + MutualTLSScheme, } // JSONSchema is used to generate a custom jsonschema @@ -218,22 +220,22 @@ func (ss SecurityScheme) Validate() error { switch ss.Type { case APIKeyScheme: if ss.APIKeyAuthConfig == nil { - return (APIKeyAuthConfig{}).Validate() + ss.APIKeyAuthConfig = &APIKeyAuthConfig{} } return ss.APIKeyAuthConfig.Validate() case HTTPAuthScheme: if ss.HTTPAuthConfig == nil { - return (HTTPAuthConfig{}).Validate() + ss.HTTPAuthConfig = &HTTPAuthConfig{} } return ss.HTTPAuthConfig.Validate() case OAuth2Scheme: if ss.OAuth2Config == nil { - return (OAuth2Config{}).Validate() + ss.OAuth2Config = &OAuth2Config{} } return ss.OAuth2Config.Validate() case OpenIDConnectScheme: if ss.OpenIDConfig == nil { - return (OpenIDConfig{}).Validate() + ss.OpenIDConfig = &OpenIDConfig{} } return ss.OpenIDConfig.Validate() } diff --git a/schema/env.go b/schema/env.go index 8f9daf9..2e31949 100644 --- a/schema/env.go +++ b/schema/env.go @@ -218,14 +218,7 @@ func (j *EnvString) UnmarshalJSON(b []byte) error { return err } - value := FindEnvTemplate(rawValue) - if value != nil { - j.EnvTemplate = *value - j.Value() - } else { - j.value = &rawValue - } - return nil + return j.unmarshalText(rawValue) } // MarshalYAML implements yaml.Marshaler interface @@ -241,12 +234,21 @@ func (j *EnvString) UnmarshalYAML(node *yaml.Node) error { if node.Value == "" { return nil } - value := FindEnvTemplate(node.Value) + return j.unmarshalText(node.Value) +} + +// UnmarshalText decodes the integer slice from string +func (j *EnvString) UnmarshalText(text []byte) error { + return j.unmarshalText(string(text)) +} + +func (j *EnvString) unmarshalText(rawValue string) error { + value := FindEnvTemplate(rawValue) if value != nil { j.EnvTemplate = *value j.Value() } else { - j.value = &node.Value + j.value = &rawValue } return nil } @@ -265,7 +267,7 @@ func NewEnvStringTemplate(template EnvTemplate) *EnvString { } } -// EnvInts implements the integer environment encoder and decoder +// EnvInt implements the integer environment encoder and decoder type EnvInt struct { value *int64 EnvTemplate @@ -333,22 +335,7 @@ func (j *EnvInt) UnmarshalJSON(b []byte) error { return err } - value := FindEnvTemplate(rawValue) - if value != nil { - j.EnvTemplate = *value - _, err := j.Value() - return err - } - if rawValue != "" { - intValue, err := strconv.ParseInt(rawValue, 10, 64) - if err != nil { - return err - } - - j.value = &intValue - } - - return nil + return j.unmarshalText(rawValue) } // MarshalYAML implements yaml.Marshaler interface @@ -364,14 +351,23 @@ func (j *EnvInt) UnmarshalYAML(node *yaml.Node) error { if node.Value == "" { return nil } - value := FindEnvTemplate(node.Value) + return j.unmarshalText(node.Value) +} + +// UnmarshalText decodes the integer slice from string +func (j *EnvInt) UnmarshalText(text []byte) error { + return j.unmarshalText(string(text)) +} + +func (j *EnvInt) unmarshalText(rawValue string) error { + value := FindEnvTemplate(rawValue) if value != nil { j.EnvTemplate = *value _, err := j.Value() return err } - if node.Value != "" { - intValue, err := strconv.ParseInt(node.Value, 10, 64) + if rawValue != "" { + intValue, err := strconv.ParseInt(rawValue, 10, 64) if err != nil { return err } @@ -472,23 +468,7 @@ func (j *EnvInts) UnmarshalJSON(b []byte) error { return err } - value := FindEnvTemplate(rawValue) - if value != nil { - j.EnvTemplate = *value - _, err := j.Value() - return err - } - - if rawValue != "" { - intValues, err := parseIntsFromString(rawValue) - if err != nil { - return err - } - - j.value = intValues - } - - return nil + return j.unmarshalText(rawValue) } // MarshalYAML implements yaml.Marshaler. @@ -505,15 +485,24 @@ func (j *EnvInts) UnmarshalYAML(node *yaml.Node) error { return nil } - value := FindEnvTemplate(node.Value) + return j.unmarshalText(node.Value) +} + +// UnmarshalText decodes the integer slice from string +func (j *EnvInts) UnmarshalText(text []byte) error { + return j.unmarshalText(string(text)) +} + +func (j *EnvInts) unmarshalText(rawValue string) error { + value := FindEnvTemplate(rawValue) if value != nil { j.EnvTemplate = *value _, err := j.Value() return err } - if node.Value != "" { - intValues, err := parseIntsFromString(node.Value) + if rawValue != "" { + intValues, err := parseIntsFromString(rawValue) if err != nil { return err } @@ -558,3 +547,268 @@ func parseIntsFromString(input string) ([]int64, error) { return intValues, nil } + +// EnvBoolean implements the boolean environment encoder and decoder +type EnvBoolean struct { + value *bool + EnvTemplate +} + +// NewEnvBooleanValue creates an EnvBoolean from value +func NewEnvBooleanValue(value bool) *EnvBoolean { + return &EnvBoolean{ + value: &value, + } +} + +// NewEnvBooleanTemplate creates an EnvBoolean from template +func NewEnvBooleanTemplate(template EnvTemplate) *EnvBoolean { + return &EnvBoolean{ + EnvTemplate: template, + } +} + +// JSONSchema is used to generate a custom jsonschema +func (j EnvBoolean) JSONSchema() *jsonschema.Schema { + return &jsonschema.Schema{ + OneOf: []*jsonschema.Schema{ + {Type: "boolean"}, + {Type: "string"}, + }, + } +} + +// WithValue returns a new EnvBoolean instance with new value +func (j EnvBoolean) WithValue(value bool) *EnvBoolean { + j.value = &value + return &j +} + +// String implements the Stringer interface +func (et EnvBoolean) String() string { + if et.IsEmpty() { + if et.value == nil { + return "" + } + return strconv.FormatBool(*et.value) + } + return et.EnvTemplate.String() +} + +// MarshalJSON implements json.Marshaler. +func (j EnvBoolean) MarshalJSON() ([]byte, error) { + if j.EnvTemplate.IsEmpty() { + return json.Marshal(j.value) + } + return j.EnvTemplate.MarshalJSON() +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *EnvBoolean) UnmarshalJSON(b []byte) error { + var v bool + if err := json.Unmarshal(b, &v); err == nil { + j.value = &v + return nil + } + + var rawValue string + if err := json.Unmarshal(b, &rawValue); err != nil { + return err + } + + return j.unmarshalText(rawValue) +} + +// UnmarshalText decodes boolean from string +func (j *EnvBoolean) UnmarshalText(text []byte) error { + return j.unmarshalText(string(text)) +} + +func (j *EnvBoolean) unmarshalText(rawValue string) error { + value := FindEnvTemplate(rawValue) + if value != nil { + j.EnvTemplate = *value + _, err := j.Value() + return err + } + if rawValue != "" { + boolValue, err := strconv.ParseBool(rawValue) + if err != nil { + return err + } + + j.value = &boolValue + } + + return nil +} + +// MarshalYAML implements yaml.Marshaler interface +func (j EnvBoolean) MarshalYAML() (any, error) { + if j.EnvTemplate.IsEmpty() { + return j.value, nil + } + return j.EnvTemplate.MarshalYAML() +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnvBoolean) UnmarshalYAML(node *yaml.Node) error { + if node.Value == "" { + return nil + } + return j.unmarshalText(node.Value) +} + +// Value returns the value which is retrieved from system or the default value if exist +func (et *EnvBoolean) Value() (*bool, error) { + if et.value != nil { + v := *et.value + return &v, nil + } + + strValue, ok := et.EnvTemplate.Value() + if !ok && strValue == "" { + return nil, nil + } + + boolValue, err := strconv.ParseBool(strValue) + if err != nil { + return nil, err + } + + if ok { + et.value = &boolValue + } + + copyVal := boolValue + return ©Val, nil +} + +// EnvStrings implements the string slice environment encoder and decoder +type EnvStrings struct { + value []string + EnvTemplate +} + +// NewEnvStringsValue creates EnvStrings from value +func NewEnvStringsValue(value []string) *EnvStrings { + return &EnvStrings{ + value: value, + } +} + +// NewEnvStringsTemplate creates EnvStrings from template +func NewEnvStringsTemplate(template EnvTemplate) *EnvStrings { + return &EnvStrings{ + EnvTemplate: template, + } +} + +// JSONSchema is used to generate a custom jsonschema +func (j EnvStrings) JSONSchema() *jsonschema.Schema { + return &jsonschema.Schema{ + OneOf: []*jsonschema.Schema{ + {Type: "string"}, + {Type: "array", Items: &jsonschema.Schema{Type: "string"}}, + }, + } +} + +// WithValue returns a new EnvStrings instance with new value +func (j EnvStrings) WithValue(value []string) *EnvStrings { + j.value = value + return &j +} + +// String implements the Stringer interface +func (et EnvStrings) String() string { + if et.IsEmpty() { + return fmt.Sprintf("%v", et.value) + } + return et.EnvTemplate.String() +} + +// MarshalJSON implements json.Marshaler. +func (j EnvStrings) MarshalJSON() ([]byte, error) { + if j.EnvTemplate.IsEmpty() { + return json.Marshal(j.value) + } + return j.EnvTemplate.MarshalJSON() +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *EnvStrings) UnmarshalJSON(b []byte) error { + var v []string + if err := json.Unmarshal(b, &v); err == nil { + j.value = v + return nil + } + + var rawValue string + if err := json.Unmarshal(b, &rawValue); err != nil { + return err + } + + return j.unmarshalText(rawValue) +} + +// MarshalYAML implements yaml.Marshaler. +func (j EnvStrings) MarshalYAML() (any, error) { + if j.EnvTemplate.IsEmpty() { + return j.value, nil + } + return j.EnvTemplate.MarshalYAML() +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (j *EnvStrings) UnmarshalYAML(node *yaml.Node) error { + if node.Value == "" { + return nil + } + + return j.unmarshalText(node.Value) +} + +// UnmarshalText decodes the integer slice from string +func (j *EnvStrings) UnmarshalText(text []byte) error { + return j.unmarshalText(string(text)) +} + +func (j *EnvStrings) unmarshalText(rawValue string) error { + value := FindEnvTemplate(rawValue) + if value != nil { + j.EnvTemplate = *value + _, err := j.Value() + return err + } + + if rawValue != "" { + values := strings.Split(rawValue, ",") + j.value = make([]string, len(values)) + for i, v := range values { + j.value[i] = strings.TrimSpace(v) + } + } else { + j.value = []string{} + } + + return nil +} + +// Value returns the value which is retrieved from system or the default value if exist +func (et *EnvStrings) Value() ([]string, error) { + if et.value != nil { + return et.value, nil + } + + strValue, ok := et.EnvTemplate.Value() + if !ok && strValue == "" { + return nil, nil + } + + err := et.unmarshalText(strValue) + if err != nil { + return nil, err + } + return et.value, nil +} diff --git a/schema/env_test.go b/schema/env_test.go index aade4c6..d1f0ff6 100644 --- a/schema/env_test.go +++ b/schema/env_test.go @@ -147,6 +147,7 @@ func TestEnvString(t *testing.T) { t.Fatal(t, err) } assertDeepEqual(t, strings.Trim(tc.input, `"`), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) + result.JSONSchema() }) } } @@ -198,6 +199,7 @@ func TestEnvInt(t *testing.T) { t.Fatal(t, err) } assertDeepEqual(t, strings.Trim(tc.input, `"`), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) + result.JSONSchema() }) } } @@ -256,6 +258,143 @@ func TestEnvInts(t *testing.T) { t.Fatal(t, err) } assertDeepEqual(t, tc.expectedYaml, strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) + result.JSONSchema() + }) + } +} + +func TestEnvBoolean(t *testing.T) { + t.Setenv("TEST_BOOL", "false") + testCases := []struct { + input string + expected EnvBoolean + }{ + { + input: `false`, + expected: *NewEnvBooleanValue(false), + }, + { + input: `"true"`, + expected: *NewEnvBooleanValue(true), + }, + { + input: `"{{FOO:-true}}"`, + expected: EnvBoolean{ + value: toPtr(true), + EnvTemplate: EnvTemplate{ + Name: "FOO", + DefaultValue: toPtr("true"), + }, + }, + }, + { + input: fmt.Sprintf(`"%s"`, NewEnvBooleanTemplate(EnvTemplate{ + Name: "TEST_BOOL", + }).String()), + expected: EnvBoolean{ + value: toPtr(false), + EnvTemplate: EnvTemplate{ + Name: "TEST_BOOL", + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.input, func(t *testing.T) { + var result EnvBoolean + if err := json.Unmarshal([]byte(tc.input), &result); err != nil { + t.Error(t, err) + t.FailNow() + } + assertDeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) + assertDeepEqual(t, tc.expected.value, result.value) + + if err := yaml.Unmarshal([]byte(tc.input), &result); err != nil { + t.Error(t, err) + t.FailNow() + } + assertDeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) + assertDeepEqual(t, tc.expected.value, result.value) + assertDeepEqual(t, strings.Trim(tc.input, "\""), tc.expected.String()) + bs, err := yaml.Marshal(result) + if err != nil { + t.Fatal(t, err) + } + assertDeepEqual(t, strings.Trim(tc.input, `"`), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) + result.JSONSchema() + if err = (&EnvBoolean{}).UnmarshalText([]byte(strings.Trim(tc.input, `"`))); err != nil { + t.Error(t, err) + t.FailNow() + } + }) + } +} + +func TestEnvStrings(t *testing.T) { + t.Setenv("TEST_STRINGS", "a,b,c") + testCases := []struct { + input string + expected EnvStrings + expectedYaml string + }{ + { + input: `["foo", "bar"]`, + expected: *NewEnvStringsValue([]string{"foo", "bar"}), + expectedYaml: `- foo +- bar`, + }, + { + input: `"foo, baz"`, + expected: *NewEnvStringsValue(nil).WithValue([]string{"foo", "baz"}), + expectedYaml: `- foo +- baz`, + }, + { + input: fmt.Sprintf(`"%s"`, NewEnvStringsTemplate(NewEnvTemplate("TEST_STRINGS")).String()), + expected: EnvStrings{ + value: []string{"a", "b", "c"}, + EnvTemplate: EnvTemplate{ + Name: "TEST_STRINGS", + }, + }, + expectedYaml: `{{TEST_STRINGS}}`, + }, + { + input: `"{{FOO:-foo, bar}}"`, + expected: EnvStrings{ + value: []string{"foo", "bar"}, + EnvTemplate: EnvTemplate{ + Name: "FOO", + DefaultValue: toPtr("foo, bar"), + }, + }, + expectedYaml: `{{FOO:-foo, bar}}`, + }, + } + + for _, tc := range testCases { + t.Run(tc.input, func(t *testing.T) { + var result EnvStrings + if err := json.Unmarshal([]byte(tc.input), &result); err != nil { + t.Error(t, err) + t.FailNow() + } + assertDeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) + assertDeepEqual(t, tc.expected.value, result.value) + + if err := yaml.Unmarshal([]byte(tc.input), &result); err != nil { + t.Error(t, err) + t.FailNow() + } + assertDeepEqual(t, tc.expected.String(), result.String()) + assertDeepEqual(t, tc.expected.value, result.value) + bs, err := yaml.Marshal(result) + if err != nil { + t.Fatal(t, err) + } + assertDeepEqual(t, tc.expectedYaml, strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) + result.JSONSchema() }) } } diff --git a/schema/setting.go b/schema/setting.go index 90f4634..953ad6d 100644 --- a/schema/setting.go +++ b/schema/setting.go @@ -101,37 +101,6 @@ func (rs RetryPolicySetting) Validate() error { return nil } -// TLSConfig represents the transport layer security (LTS) configuration for the HTTP client -type TLSConfig struct { - // Path to the TLS cert to use for TLS required connections. - CertFile string `json:"certFile,omitempty" yaml:"certFile,omitempty" mapstructure:"certFile"` - // Alternative to cert_file. Provide the certificate contents as a string instead of a filepath. - CertPem string `json:"certPem,omitempty" yaml:"certPem,omitempty" mapstructure:"certPem"` - // Path to the TLS key to use for TLS required connections. - KeyFile string `json:"keyFile,omitempty" yaml:"keyFile,omitempty" mapstructure:"keyFile"` - // Alternative to key_file. Provide the key contents as a string instead of a filepath. - KeyPem string `json:"keyPem,omitempty" yaml:"keyPem,omitempty" mapstructure:"keyPem"` - // Path to the CA cert. For a client this verifies the server certificate. For a server this verifies client certificates. - // If empty uses system root CA. - CAFile string `json:"caFile,omitempty" yaml:"caFile,omitempty" mapstructure:"caFile"` - // Alternative to ca_file. Provide the CA cert contents as a string instead of a filepath. - CAPem string `json:"caPem,omitempty" yaml:"caPem,omitempty" mapstructure:"caPem"` - // Additionally you can configure TLS to be enabled but skip verifying the server's certificate chain. - InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" mapstructure:"insecureSkipVerify"` - // Whether to load the system certificate authorities pool alongside the certificate authority. - IncludeSystemCACertsPool bool `json:"includeSystemCACertsPool,omitempty" yaml:"includeSystemCACertsPool,omitempty" mapstructure:"includeSystemCACertsPool"` - // Minimum acceptable TLS version. - MinVersion string `json:"minVersion,omitempty" yaml:"minVersion,omitempty" mapstructure:"minVersion"` - // Maximum acceptable TLS version. - MaxVersion string `json:"maxVersion,omitempty" yaml:"maxVersion,omitempty" mapstructure:"maxVersion"` - // Explicit cipher suites can be set. If left blank, a safe default list is used. - // See https://go.dev/src/crypto/tls/cipher_suites.go for a list of supported cipher suites. - CipherSuites []string `json:"cipherSuites,omitempty" yaml:"cipherSuites,omitempty" mapstructure:"cipherSuites"` - // Specifies the duration after which the certificate will be reloaded. If not set, it will never be reloaded. - // The interval unit is minute - ReloadInterval uint `json:"reloadInterval,omitempty" yaml:"reloadInterval,omitempty" mapstructure:"reloadInterval"` -} - // ServerConfig contains server configurations type ServerConfig struct { URL EnvString `json:"url" yaml:"url" mapstructure:"url"` @@ -142,8 +111,7 @@ type ServerConfig struct { Retry *RetryPolicySetting `json:"retry,omitempty" yaml:"retry,omitempty" mapstructure:"retry"` SecuritySchemes map[string]SecurityScheme `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty" mapstructure:"securitySchemes"` Security AuthSecurities `json:"security,omitempty" yaml:"security,omitempty" mapstructure:"security"` - // The transport layer security (LTS) configuration for the HTTP client to communicate the current server - TLS *TLSConfig `json:"tls,omitempty" yaml:"tls,omitempty" mapstructure:"tls"` + TLS *TLSConfig `json:"tls,omitempty" yaml:"tls,omitempty" mapstructure:"tls"` } // Validate if the current instance is valid @@ -177,3 +145,39 @@ func parseRelativeOrHttpURL(input string) (*url.URL, error) { } return parseHttpURL(input) } + +// TLSConfig represents the transport layer security (LTS) configuration for the mutualTLS authentication +type TLSConfig struct { + // Path to the TLS cert to use for TLS required connections. + CertFile *EnvString `json:"certFile,omitempty" yaml:"certFile,omitempty" mapstructure:"certFile"` + // Alternative to cert_file. Provide the certificate contents as a string instead of a filepath. + CertPem *EnvString `json:"certPem,omitempty" yaml:"certPem,omitempty" mapstructure:"certPem"` + // Path to the TLS key to use for TLS required connections. + KeyFile *EnvString `json:"keyFile,omitempty" yaml:"keyFile,omitempty" mapstructure:"keyFile"` + // Alternative to key_file. Provide the key contents as a string instead of a filepath. + KeyPem *EnvString `json:"keyPem,omitempty" yaml:"keyPem,omitempty" mapstructure:"keyPem"` + // Path to the CA cert. For a client this verifies the server certificate. For a server this verifies client certificates. + // If empty uses system root CA. + CAFile *EnvString `json:"caFile,omitempty" yaml:"caFile,omitempty" mapstructure:"caFile"` + // Alternative to ca_file. Provide the CA cert contents as a string instead of a filepath. + CAPem *EnvString `json:"caPem,omitempty" yaml:"caPem,omitempty" mapstructure:"caPem"` + // Additionally you can configure TLS to be enabled but skip verifying the server's certificate chain. + InsecureSkipVerify *EnvBoolean `json:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" mapstructure:"insecureSkipVerify"` + // Whether to load the system certificate authorities pool alongside the certificate authority. + IncludeSystemCACertsPool *EnvBoolean `json:"includeSystemCACertsPool,omitempty" yaml:"includeSystemCACertsPool,omitempty" mapstructure:"includeSystemCACertsPool"` + // Minimum acceptable TLS version. + MinVersion *EnvString `json:"minVersion,omitempty" yaml:"minVersion,omitempty" mapstructure:"minVersion"` + // Maximum acceptable TLS version. + MaxVersion *EnvString `json:"maxVersion,omitempty" yaml:"maxVersion,omitempty" mapstructure:"maxVersion"` + // Explicit cipher suites can be set. If left blank, a safe default list is used. + // See https://go.dev/src/crypto/tls/cipher_suites.go for a list of supported cipher suites. + CipherSuites *EnvStrings `json:"cipherSuites,omitempty" yaml:"cipherSuites,omitempty" mapstructure:"cipherSuites"` + // Specifies the duration after which the certificate will be reloaded. If not set, it will never be reloaded. + // The interval unit is minute + ReloadInterval *EnvInt `json:"reloadInterval,omitempty" yaml:"reloadInterval,omitempty" mapstructure:"reloadInterval"` +} + +// Validate if the current instance is valid +func (ss TLSConfig) Validate() error { + return nil +}