| ... | ... |
@@ -94,12 +94,13 @@ func (o *ValidateMasterConfigOptions) Run() (bool, error) {
|
| 94 | 94 |
} |
| 95 | 95 |
|
| 96 | 96 |
const ( |
| 97 |
- minColumnWidth = 4 |
|
| 98 |
- tabWidth = 4 |
|
| 99 |
- padding = 2 |
|
| 100 |
- padchar = byte(' ')
|
|
| 101 |
- flags = 0 |
|
| 102 |
- validationErrorHeadings = "ERROR\tFIELD\tVALUE\tDETAILS\n" |
|
| 97 |
+ minColumnWidth = 4 |
|
| 98 |
+ tabWidth = 4 |
|
| 99 |
+ padding = 2 |
|
| 100 |
+ padchar = byte(' ')
|
|
| 101 |
+ flags = 0 |
|
| 102 |
+ validationErrorHeadings = "ERROR\tFIELD\tVALUE\tDETAILS\n" |
|
| 103 |
+ validationWarningHeadings = "WARNING\tFIELD\tVALUE\tDETAILS\n" |
|
| 103 | 104 |
) |
| 104 | 105 |
|
| 105 | 106 |
// prettyPrintValidationResults prints the contents of the ValidationResults into the buffer of a tabwriter.Writer. |
| ... | ... |
@@ -107,14 +108,14 @@ const ( |
| 107 | 107 |
func prettyPrintValidationResults(results validation.ValidationResults, writer *tabwriter.Writer) error {
|
| 108 | 108 |
if len(results.Errors) > 0 {
|
| 109 | 109 |
fmt.Fprintf(writer, "VALIDATION ERRORS:\t\t\t\n") |
| 110 |
- err := prettyPrintValidationErrorList(results.Errors, writer) |
|
| 110 |
+ err := prettyPrintValidationErrorList(validationErrorHeadings, results.Errors, writer) |
|
| 111 | 111 |
if err != nil {
|
| 112 | 112 |
return err |
| 113 | 113 |
} |
| 114 | 114 |
} |
| 115 | 115 |
if len(results.Warnings) > 0 {
|
| 116 | 116 |
fmt.Fprintf(writer, "VALIDATION WARNINGS:\t\t\t\n") |
| 117 |
- err := prettyPrintValidationErrorList(results.Errors, writer) |
|
| 117 |
+ err := prettyPrintValidationErrorList(validationWarningHeadings, results.Warnings, writer) |
|
| 118 | 118 |
if err != nil {
|
| 119 | 119 |
return err |
| 120 | 120 |
} |
| ... | ... |
@@ -124,9 +125,9 @@ func prettyPrintValidationResults(results validation.ValidationResults, writer * |
| 124 | 124 |
|
| 125 | 125 |
// prettyPrintValidationErrorList prints the contents of the ValidationErrorList into the buffer of a tabwriter.Writer. |
| 126 | 126 |
// The writer must be Flush()ed after calling this to write the buffered data. |
| 127 |
-func prettyPrintValidationErrorList(validationErrors fielderrors.ValidationErrorList, writer *tabwriter.Writer) error {
|
|
| 127 |
+func prettyPrintValidationErrorList(headings string, validationErrors fielderrors.ValidationErrorList, writer *tabwriter.Writer) error {
|
|
| 128 | 128 |
if len(validationErrors) > 0 {
|
| 129 |
- fmt.Fprintf(writer, validationErrorHeadings) |
|
| 129 |
+ fmt.Fprintf(writer, headings) |
|
| 130 | 130 |
for _, err := range validationErrors {
|
| 131 | 131 |
switch validationError := err.(type) {
|
| 132 | 132 |
case (*fielderrors.ValidationError): |
| ... | ... |
@@ -84,10 +84,10 @@ func (o *ValidateNodeConfigOptions) Run() (ok bool, err error) {
|
| 84 | 84 |
|
| 85 | 85 |
results := validation.ValidateNodeConfig(nodeConfig) |
| 86 | 86 |
writer := tabwriter.NewWriter(o.Out, minColumnWidth, tabWidth, padding, padchar, flags) |
| 87 |
- err = prettyPrintValidationErrorList(results, writer) |
|
| 87 |
+ err = prettyPrintValidationResults(results, writer) |
|
| 88 | 88 |
if err != nil {
|
| 89 |
- return len(results) == 0, fmt.Errorf("could not print results: %v", err)
|
|
| 89 |
+ return len(results.Errors) == 0, fmt.Errorf("could not print results: %v", err)
|
|
| 90 | 90 |
} |
| 91 | 91 |
writer.Flush() |
| 92 |
- return len(results) == 0, nil |
|
| 92 |
+ return len(results.Errors) == 0, nil |
|
| 93 | 93 |
} |
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package api |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "crypto/tls" |
|
| 4 | 5 |
"crypto/x509" |
| 5 | 6 |
"fmt" |
| 6 | 7 |
"net" |
| ... | ... |
@@ -120,6 +121,10 @@ func GetMasterFileReferences(config *MasterConfig) []*string {
|
| 120 | 120 |
refs = append(refs, &config.ServingInfo.ServerCert.CertFile) |
| 121 | 121 |
refs = append(refs, &config.ServingInfo.ServerCert.KeyFile) |
| 122 | 122 |
refs = append(refs, &config.ServingInfo.ClientCA) |
| 123 |
+ for i := range config.ServingInfo.NamedCertificates {
|
|
| 124 |
+ refs = append(refs, &config.ServingInfo.NamedCertificates[i].CertFile) |
|
| 125 |
+ refs = append(refs, &config.ServingInfo.NamedCertificates[i].KeyFile) |
|
| 126 |
+ } |
|
| 123 | 127 |
|
| 124 | 128 |
refs = append(refs, &config.EtcdClientInfo.ClientCert.CertFile) |
| 125 | 129 |
refs = append(refs, &config.EtcdClientInfo.ClientCert.KeyFile) |
| ... | ... |
@@ -133,10 +138,18 @@ func GetMasterFileReferences(config *MasterConfig) []*string {
|
| 133 | 133 |
refs = append(refs, &config.EtcdConfig.ServingInfo.ServerCert.CertFile) |
| 134 | 134 |
refs = append(refs, &config.EtcdConfig.ServingInfo.ServerCert.KeyFile) |
| 135 | 135 |
refs = append(refs, &config.EtcdConfig.ServingInfo.ClientCA) |
| 136 |
+ for i := range config.EtcdConfig.ServingInfo.NamedCertificates {
|
|
| 137 |
+ refs = append(refs, &config.EtcdConfig.ServingInfo.NamedCertificates[i].CertFile) |
|
| 138 |
+ refs = append(refs, &config.EtcdConfig.ServingInfo.NamedCertificates[i].KeyFile) |
|
| 139 |
+ } |
|
| 136 | 140 |
|
| 137 | 141 |
refs = append(refs, &config.EtcdConfig.PeerServingInfo.ServerCert.CertFile) |
| 138 | 142 |
refs = append(refs, &config.EtcdConfig.PeerServingInfo.ServerCert.KeyFile) |
| 139 | 143 |
refs = append(refs, &config.EtcdConfig.PeerServingInfo.ClientCA) |
| 144 |
+ for i := range config.EtcdConfig.PeerServingInfo.NamedCertificates {
|
|
| 145 |
+ refs = append(refs, &config.EtcdConfig.PeerServingInfo.NamedCertificates[i].CertFile) |
|
| 146 |
+ refs = append(refs, &config.EtcdConfig.PeerServingInfo.NamedCertificates[i].KeyFile) |
|
| 147 |
+ } |
|
| 140 | 148 |
|
| 141 | 149 |
refs = append(refs, &config.EtcdConfig.StorageDir) |
| 142 | 150 |
} |
| ... | ... |
@@ -182,6 +195,11 @@ func GetMasterFileReferences(config *MasterConfig) []*string {
|
| 182 | 182 |
refs = append(refs, &config.AssetConfig.ServingInfo.ServerCert.CertFile) |
| 183 | 183 |
refs = append(refs, &config.AssetConfig.ServingInfo.ServerCert.KeyFile) |
| 184 | 184 |
refs = append(refs, &config.AssetConfig.ServingInfo.ClientCA) |
| 185 |
+ for i := range config.AssetConfig.ServingInfo.NamedCertificates {
|
|
| 186 |
+ refs = append(refs, &config.AssetConfig.ServingInfo.NamedCertificates[i].CertFile) |
|
| 187 |
+ refs = append(refs, &config.AssetConfig.ServingInfo.NamedCertificates[i].KeyFile) |
|
| 188 |
+ } |
|
| 189 |
+ |
|
| 185 | 190 |
for i := range config.AssetConfig.ExtensionScripts {
|
| 186 | 191 |
refs = append(refs, &config.AssetConfig.ExtensionScripts[i]) |
| 187 | 192 |
} |
| ... | ... |
@@ -228,6 +246,10 @@ func GetNodeFileReferences(config *NodeConfig) []*string {
|
| 228 | 228 |
refs = append(refs, &config.ServingInfo.ServerCert.CertFile) |
| 229 | 229 |
refs = append(refs, &config.ServingInfo.ServerCert.KeyFile) |
| 230 | 230 |
refs = append(refs, &config.ServingInfo.ClientCA) |
| 231 |
+ for i := range config.ServingInfo.NamedCertificates {
|
|
| 232 |
+ refs = append(refs, &config.ServingInfo.NamedCertificates[i].CertFile) |
|
| 233 |
+ refs = append(refs, &config.ServingInfo.NamedCertificates[i].KeyFile) |
|
| 234 |
+ } |
|
| 231 | 235 |
|
| 232 | 236 |
refs = append(refs, &config.MasterKubeConfig) |
| 233 | 237 |
|
| ... | ... |
@@ -317,6 +339,26 @@ func GetAPIClientCertCAPool(options MasterConfig) (*x509.CertPool, error) {
|
| 317 | 317 |
return cmdutil.CertPoolFromFile(options.ServingInfo.ClientCA) |
| 318 | 318 |
} |
| 319 | 319 |
|
| 320 |
+// GetNamedCertificateMap returns a map of strings to *tls.Certificate, suitable for use in tls.Config#NamedCertificates |
|
| 321 |
+// Returns an error if any of the certs cannot be loaded, or do not match the configured name |
|
| 322 |
+// Returns nil if len(namedCertificates) == 0 |
|
| 323 |
+func GetNamedCertificateMap(namedCertificates []NamedCertificate) (map[string]*tls.Certificate, error) {
|
|
| 324 |
+ if len(namedCertificates) == 0 {
|
|
| 325 |
+ return nil, nil |
|
| 326 |
+ } |
|
| 327 |
+ namedCerts := map[string]*tls.Certificate{}
|
|
| 328 |
+ for _, namedCertificate := range namedCertificates {
|
|
| 329 |
+ cert, err := tls.LoadX509KeyPair(namedCertificate.CertFile, namedCertificate.KeyFile) |
|
| 330 |
+ if err != nil {
|
|
| 331 |
+ return nil, err |
|
| 332 |
+ } |
|
| 333 |
+ for _, name := range namedCertificate.Names {
|
|
| 334 |
+ namedCerts[name] = &cert |
|
| 335 |
+ } |
|
| 336 |
+ } |
|
| 337 |
+ return namedCerts, nil |
|
| 338 |
+} |
|
| 339 |
+ |
|
| 320 | 340 |
// GetClientCertCAPool returns a cert pool containing all client CAs that could be presented (union of API and OAuth) |
| 321 | 341 |
func GetClientCertCAPool(options MasterConfig) (*x509.CertPool, error) {
|
| 322 | 342 |
roots := x509.NewCertPool() |
| ... | ... |
@@ -339,6 +339,17 @@ type ServingInfo struct {
|
| 339 | 339 |
ServerCert CertInfo |
| 340 | 340 |
// ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates |
| 341 | 341 |
ClientCA string |
| 342 |
+ // NamedCertificates is a list of certificates to use to secure requests to specific hostnames |
|
| 343 |
+ NamedCertificates []NamedCertificate |
|
| 344 |
+} |
|
| 345 |
+ |
|
| 346 |
+// NamedCertificate specifies a certificate/key, and the names it should be served for |
|
| 347 |
+type NamedCertificate struct {
|
|
| 348 |
+ // Names is a list of DNS names this certificate should be used to secure |
|
| 349 |
+ // A name can be a normal DNS name, or can contain leading wildcard segments. |
|
| 350 |
+ Names []string |
|
| 351 |
+ // CertInfo is the TLS cert info for serving secure traffic |
|
| 352 |
+ CertInfo |
|
| 342 | 353 |
} |
| 343 | 354 |
|
| 344 | 355 |
type HTTPServingInfo struct {
|
| ... | ... |
@@ -141,17 +141,17 @@ func init() {
|
| 141 | 141 |
return s.DefaultConvert(in, out, conversion.IgnoreMissingFields) |
| 142 | 142 |
}, |
| 143 | 143 |
func(in *ServingInfo, out *newer.ServingInfo, s conversion.Scope) error {
|
| 144 |
- out.BindAddress = in.BindAddress |
|
| 145 |
- out.BindNetwork = in.BindNetwork |
|
| 146 |
- out.ClientCA = in.ClientCA |
|
| 144 |
+ if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
|
|
| 145 |
+ return err |
|
| 146 |
+ } |
|
| 147 | 147 |
out.ServerCert.CertFile = in.CertFile |
| 148 | 148 |
out.ServerCert.KeyFile = in.KeyFile |
| 149 | 149 |
return nil |
| 150 | 150 |
}, |
| 151 | 151 |
func(in *newer.ServingInfo, out *ServingInfo, s conversion.Scope) error {
|
| 152 |
- out.BindAddress = in.BindAddress |
|
| 153 |
- out.BindNetwork = in.BindNetwork |
|
| 154 |
- out.ClientCA = in.ClientCA |
|
| 152 |
+ if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil {
|
|
| 153 |
+ return err |
|
| 154 |
+ } |
|
| 155 | 155 |
out.CertFile = in.ServerCert.CertFile |
| 156 | 156 |
out.KeyFile = in.ServerCert.KeyFile |
| 157 | 157 |
return nil |
| ... | ... |
@@ -319,6 +319,17 @@ type ServingInfo struct {
|
| 319 | 319 |
CertInfo `json:",inline"` |
| 320 | 320 |
// ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates |
| 321 | 321 |
ClientCA string `json:"clientCA"` |
| 322 |
+ // NamedCertificates is a list of certificates to use to secure requests to specific hostnames |
|
| 323 |
+ NamedCertificates []NamedCertificate `json:"namedCertificates"` |
|
| 324 |
+} |
|
| 325 |
+ |
|
| 326 |
+// NamedCertificate specifies a certificate/key, and the names it should be served for |
|
| 327 |
+type NamedCertificate struct {
|
|
| 328 |
+ // Names is a list of DNS names this certificate should be used to secure |
|
| 329 |
+ // A name can be a normal DNS name, or can contain leading wildcard segments. |
|
| 330 |
+ Names []string `json:"names"` |
|
| 331 |
+ // CertInfo is the TLS cert info for serving secure traffic |
|
| 332 |
+ CertInfo `json:",inline"` |
|
| 322 | 333 |
} |
| 323 | 334 |
|
| 324 | 335 |
type HTTPServingInfo struct {
|
| ... | ... |
@@ -46,6 +46,7 @@ servingInfo: |
| 46 | 46 |
certFile: "" |
| 47 | 47 |
clientCA: "" |
| 48 | 48 |
keyFile: "" |
| 49 |
+ namedCertificates: null |
|
| 49 | 50 |
volumeDirectory: "" |
| 50 | 51 |
` |
| 51 | 52 |
|
| ... | ... |
@@ -76,6 +77,7 @@ assetConfig: |
| 76 | 76 |
clientCA: "" |
| 77 | 77 |
keyFile: "" |
| 78 | 78 |
maxRequestsInFlight: 0 |
| 79 |
+ namedCertificates: null |
|
| 79 | 80 |
requestTimeoutSeconds: 0 |
| 80 | 81 |
controllerLeaseTTL: 0 |
| 81 | 82 |
controllers: "" |
| ... | ... |
@@ -98,12 +100,14 @@ etcdConfig: |
| 98 | 98 |
certFile: "" |
| 99 | 99 |
clientCA: "" |
| 100 | 100 |
keyFile: "" |
| 101 |
+ namedCertificates: null |
|
| 101 | 102 |
servingInfo: |
| 102 | 103 |
bindAddress: "" |
| 103 | 104 |
bindNetwork: "" |
| 104 | 105 |
certFile: "" |
| 105 | 106 |
clientCA: "" |
| 106 | 107 |
keyFile: "" |
| 108 |
+ namedCertificates: null |
|
| 107 | 109 |
storageDirectory: "" |
| 108 | 110 |
etcdStorageConfig: |
| 109 | 111 |
kubernetesStoragePrefix: "" |
| ... | ... |
@@ -276,6 +280,10 @@ servingInfo: |
| 276 | 276 |
clientCA: "" |
| 277 | 277 |
keyFile: "" |
| 278 | 278 |
maxRequestsInFlight: 0 |
| 279 |
+ namedCertificates: |
|
| 280 |
+ - certFile: "" |
|
| 281 |
+ keyFile: "" |
|
| 282 |
+ names: null |
|
| 279 | 283 |
requestTimeoutSeconds: 0 |
| 280 | 284 |
` |
| 281 | 285 |
) |
| ... | ... |
@@ -295,6 +303,11 @@ func TestNodeConfig(t *testing.T) {
|
| 295 | 295 |
|
| 296 | 296 |
func TestMasterConfig(t *testing.T) {
|
| 297 | 297 |
config := &internal.MasterConfig{
|
| 298 |
+ ServingInfo: internal.HTTPServingInfo{
|
|
| 299 |
+ ServingInfo: internal.ServingInfo{
|
|
| 300 |
+ NamedCertificates: []internal.NamedCertificate{{}},
|
|
| 301 |
+ }, |
|
| 302 |
+ }, |
|
| 298 | 303 |
KubernetesMasterConfig: &internal.KubernetesMasterConfig{},
|
| 299 | 304 |
EtcdConfig: &internal.EtcdConfig{},
|
| 300 | 305 |
OAuthConfig: &internal.OAuthConfig{
|
| ... | ... |
@@ -9,7 +9,7 @@ func ValidateAllInOneConfig(master *api.MasterConfig, node *api.NodeConfig) Vali |
| 9 | 9 |
|
| 10 | 10 |
validationResults.Append(ValidateMasterConfig(master).Prefix("masterConfig"))
|
| 11 | 11 |
|
| 12 |
- validationResults.AddErrors(ValidateNodeConfig(node).Prefix("nodeConfig")...)
|
|
| 12 |
+ validationResults.Append(ValidateNodeConfig(node).Prefix("nodeConfig"))
|
|
| 13 | 13 |
|
| 14 | 14 |
// Validation between the configs |
| 15 | 15 |
|
| ... | ... |
@@ -56,24 +56,31 @@ func ValidateEtcdConnectionInfo(config api.EtcdConnectionInfo, server *api.EtcdC |
| 56 | 56 |
return allErrs |
| 57 | 57 |
} |
| 58 | 58 |
|
| 59 |
-func ValidateEtcdConfig(config *api.EtcdConfig) fielderrors.ValidationErrorList {
|
|
| 60 |
- allErrs := fielderrors.ValidationErrorList{}
|
|
| 59 |
+func ValidateEtcdConfig(config *api.EtcdConfig) ValidationResults {
|
|
| 60 |
+ validationResults := ValidationResults{}
|
|
| 61 | 61 |
|
| 62 |
- allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...)
|
|
| 62 |
+ validationResults.Append(ValidateServingInfo(config.ServingInfo).Prefix("servingInfo"))
|
|
| 63 | 63 |
if config.ServingInfo.BindNetwork == "tcp6" {
|
| 64 |
- allErrs = append(allErrs, fielderrors.NewFieldInvalid("servingInfo.bindNetwork", config.ServingInfo.BindNetwork, "tcp6 is not a valid bindNetwork for etcd, must be tcp or tcp4"))
|
|
| 64 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("servingInfo.bindNetwork", config.ServingInfo.BindNetwork, "tcp6 is not a valid bindNetwork for etcd, must be tcp or tcp4"))
|
|
| 65 |
+ } |
|
| 66 |
+ if len(config.ServingInfo.NamedCertificates) > 0 {
|
|
| 67 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("servingInfo.namedCertificates", "<not shown>", "namedCertificates are not supported for etcd"))
|
|
| 65 | 68 |
} |
| 66 |
- allErrs = append(allErrs, ValidateServingInfo(config.PeerServingInfo).Prefix("peerServingInfo")...)
|
|
| 69 |
+ |
|
| 70 |
+ validationResults.Append(ValidateServingInfo(config.PeerServingInfo).Prefix("peerServingInfo"))
|
|
| 67 | 71 |
if config.ServingInfo.BindNetwork == "tcp6" {
|
| 68 |
- allErrs = append(allErrs, fielderrors.NewFieldInvalid("peerServingInfo.bindNetwork", config.ServingInfo.BindNetwork, "tcp6 is not a valid bindNetwork for etcd peers, must be tcp or tcp4"))
|
|
| 72 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("peerServingInfo.bindNetwork", config.ServingInfo.BindNetwork, "tcp6 is not a valid bindNetwork for etcd peers, must be tcp or tcp4"))
|
|
| 73 |
+ } |
|
| 74 |
+ if len(config.ServingInfo.NamedCertificates) > 0 {
|
|
| 75 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("peerServingInfo.namedCertificates", "<not shown>", "namedCertificates are not supported for etcd"))
|
|
| 69 | 76 |
} |
| 70 | 77 |
|
| 71 |
- allErrs = append(allErrs, ValidateHostPort(config.Address, "address")...) |
|
| 72 |
- allErrs = append(allErrs, ValidateHostPort(config.PeerAddress, "peerAddress")...) |
|
| 78 |
+ validationResults.AddErrors(ValidateHostPort(config.Address, "address")...) |
|
| 79 |
+ validationResults.AddErrors(ValidateHostPort(config.PeerAddress, "peerAddress")...) |
|
| 73 | 80 |
|
| 74 | 81 |
if len(config.StorageDir) == 0 {
|
| 75 |
- allErrs = append(allErrs, fielderrors.NewFieldRequired("storageDirectory"))
|
|
| 82 |
+ validationResults.AddErrors(fielderrors.NewFieldRequired("storageDirectory"))
|
|
| 76 | 83 |
} |
| 77 | 84 |
|
| 78 |
- return allErrs |
|
| 85 |
+ return validationResults |
|
| 79 | 86 |
} |
| ... | ... |
@@ -4,6 +4,7 @@ import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"net" |
| 6 | 6 |
"net/url" |
| 7 |
+ "reflect" |
|
| 7 | 8 |
"regexp" |
| 8 | 9 |
"strings" |
| 9 | 10 |
"time" |
| ... | ... |
@@ -57,8 +58,6 @@ func (r ValidationResults) Prefix(prefix string) ValidationResults {
|
| 57 | 57 |
func ValidateMasterConfig(config *api.MasterConfig) ValidationResults {
|
| 58 | 58 |
validationResults := ValidationResults{}
|
| 59 | 59 |
|
| 60 |
- validationResults.AddErrors(ValidateHTTPServingInfo(config.ServingInfo).Prefix("servingInfo")...)
|
|
| 61 |
- |
|
| 62 | 60 |
if _, urlErrs := ValidateURL(config.MasterPublicURL, "masterPublicURL"); len(urlErrs) > 0 {
|
| 63 | 61 |
validationResults.AddErrors(urlErrs...) |
| 64 | 62 |
} |
| ... | ... |
@@ -73,13 +72,16 @@ func ValidateMasterConfig(config *api.MasterConfig) ValidationResults {
|
| 73 | 73 |
validationResults.AddErrors(ValidateDisabledFeatures(config.DisabledFeatures, "disabledFeatures")...) |
| 74 | 74 |
|
| 75 | 75 |
if config.AssetConfig != nil {
|
| 76 |
- validationResults.AddErrors(ValidateAssetConfig(config.AssetConfig).Prefix("assetConfig")...)
|
|
| 76 |
+ validationResults.Append(ValidateAssetConfig(config.AssetConfig).Prefix("assetConfig"))
|
|
| 77 | 77 |
colocated := config.AssetConfig.ServingInfo.BindAddress == config.ServingInfo.BindAddress |
| 78 | 78 |
if colocated {
|
| 79 | 79 |
publicURL, _ := url.Parse(config.AssetConfig.PublicURL) |
| 80 | 80 |
if publicURL.Path == "/" {
|
| 81 | 81 |
validationResults.AddErrors(fielderrors.NewFieldInvalid("assetConfig.publicURL", config.AssetConfig.PublicURL, "path can not be / when colocated with master API"))
|
| 82 | 82 |
} |
| 83 |
+ if !reflect.DeepEqual(config.AssetConfig.ServingInfo, config.ServingInfo) {
|
|
| 84 |
+ validationResults.AddWarnings(fielderrors.NewFieldInvalid("assetConfig.servingInfo", "<not displayed>", "assetConfig.servingInfo is ignored when colocated with master API"))
|
|
| 85 |
+ } |
|
| 83 | 86 |
} |
| 84 | 87 |
|
| 85 | 88 |
if config.OAuthConfig != nil {
|
| ... | ... |
@@ -106,9 +108,9 @@ func ValidateMasterConfig(config *api.MasterConfig) ValidationResults {
|
| 106 | 106 |
|
| 107 | 107 |
if config.EtcdConfig != nil {
|
| 108 | 108 |
etcdConfigErrs := ValidateEtcdConfig(config.EtcdConfig).Prefix("etcdConfig")
|
| 109 |
- validationResults.AddErrors(etcdConfigErrs...) |
|
| 109 |
+ validationResults.Append(etcdConfigErrs) |
|
| 110 | 110 |
|
| 111 |
- if len(etcdConfigErrs) == 0 {
|
|
| 111 |
+ if len(etcdConfigErrs.Errors) == 0 {
|
|
| 112 | 112 |
// Validate the etcdClientInfo with the internal etcdConfig |
| 113 | 113 |
validationResults.AddErrors(ValidateEtcdConnectionInfo(config.EtcdClientInfo, config.EtcdConfig).Prefix("etcdClientInfo")...)
|
| 114 | 114 |
} else {
|
| ... | ... |
@@ -157,7 +159,7 @@ func ValidateMasterConfig(config *api.MasterConfig) ValidationResults {
|
| 157 | 157 |
|
| 158 | 158 |
validationResults.Append(ValidateServiceAccountConfig(config.ServiceAccountConfig, builtInKubernetes).Prefix("serviceAccountConfig"))
|
| 159 | 159 |
|
| 160 |
- validationResults.AddErrors(ValidateHTTPServingInfo(config.ServingInfo).Prefix("servingInfo")...)
|
|
| 160 |
+ validationResults.Append(ValidateHTTPServingInfo(config.ServingInfo).Prefix("servingInfo"))
|
|
| 161 | 161 |
|
| 162 | 162 |
validationResults.Append(ValidateProjectConfig(config.ProjectConfig).Prefix("projectConfig"))
|
| 163 | 163 |
|
| ... | ... |
@@ -261,65 +263,65 @@ func ValidateServiceAccountConfig(config api.ServiceAccountConfig, builtInKubern |
| 261 | 261 |
return validationResults |
| 262 | 262 |
} |
| 263 | 263 |
|
| 264 |
-func ValidateAssetConfig(config *api.AssetConfig) fielderrors.ValidationErrorList {
|
|
| 265 |
- allErrs := fielderrors.ValidationErrorList{}
|
|
| 264 |
+func ValidateAssetConfig(config *api.AssetConfig) ValidationResults {
|
|
| 265 |
+ validationResults := ValidationResults{}
|
|
| 266 | 266 |
|
| 267 |
- allErrs = append(allErrs, ValidateHTTPServingInfo(config.ServingInfo).Prefix("servingInfo")...)
|
|
| 267 |
+ validationResults.Append(ValidateHTTPServingInfo(config.ServingInfo).Prefix("servingInfo"))
|
|
| 268 | 268 |
|
| 269 | 269 |
if len(config.LogoutURL) > 0 {
|
| 270 | 270 |
_, urlErrs := ValidateURL(config.LogoutURL, "logoutURL") |
| 271 | 271 |
if len(urlErrs) > 0 {
|
| 272 |
- allErrs = append(allErrs, urlErrs...) |
|
| 272 |
+ validationResults.AddErrors(urlErrs...) |
|
| 273 | 273 |
} |
| 274 | 274 |
} |
| 275 | 275 |
|
| 276 | 276 |
urlObj, urlErrs := ValidateURL(config.PublicURL, "publicURL") |
| 277 | 277 |
if len(urlErrs) > 0 {
|
| 278 |
- allErrs = append(allErrs, urlErrs...) |
|
| 278 |
+ validationResults.AddErrors(urlErrs...) |
|
| 279 | 279 |
} |
| 280 | 280 |
if urlObj != nil {
|
| 281 | 281 |
if !strings.HasSuffix(urlObj.Path, "/") {
|
| 282 |
- allErrs = append(allErrs, fielderrors.NewFieldInvalid("publicURL", config.PublicURL, "must have a trailing slash in path"))
|
|
| 282 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("publicURL", config.PublicURL, "must have a trailing slash in path"))
|
|
| 283 | 283 |
} |
| 284 | 284 |
} |
| 285 | 285 |
|
| 286 | 286 |
if _, urlErrs := ValidateURL(config.MasterPublicURL, "masterPublicURL"); len(urlErrs) > 0 {
|
| 287 |
- allErrs = append(allErrs, urlErrs...) |
|
| 287 |
+ validationResults.AddErrors(urlErrs...) |
|
| 288 | 288 |
} |
| 289 | 289 |
|
| 290 | 290 |
if len(config.LoggingPublicURL) > 0 {
|
| 291 | 291 |
if _, loggingURLErrs := ValidateSecureURL(config.LoggingPublicURL, "loggingPublicURL"); len(loggingURLErrs) > 0 {
|
| 292 |
- allErrs = append(allErrs, loggingURLErrs...) |
|
| 292 |
+ validationResults.AddErrors(loggingURLErrs...) |
|
| 293 | 293 |
} |
| 294 | 294 |
} |
| 295 | 295 |
|
| 296 | 296 |
if len(config.MetricsPublicURL) > 0 {
|
| 297 | 297 |
if _, metricsURLErrs := ValidateSecureURL(config.MetricsPublicURL, "metricsPublicURL"); len(metricsURLErrs) > 0 {
|
| 298 |
- allErrs = append(allErrs, metricsURLErrs...) |
|
| 298 |
+ validationResults.AddErrors(metricsURLErrs...) |
|
| 299 | 299 |
} |
| 300 | 300 |
} |
| 301 | 301 |
|
| 302 | 302 |
for i, scriptFile := range config.ExtensionScripts {
|
| 303 |
- allErrs = append(allErrs, ValidateFile(scriptFile, fmt.Sprintf("extensionScripts[%d]", i))...)
|
|
| 303 |
+ validationResults.AddErrors(ValidateFile(scriptFile, fmt.Sprintf("extensionScripts[%d]", i))...)
|
|
| 304 | 304 |
} |
| 305 | 305 |
|
| 306 | 306 |
for i, stylesheetFile := range config.ExtensionStylesheets {
|
| 307 |
- allErrs = append(allErrs, ValidateFile(stylesheetFile, fmt.Sprintf("extensionStylesheets[%d]", i))...)
|
|
| 307 |
+ validationResults.AddErrors(ValidateFile(stylesheetFile, fmt.Sprintf("extensionStylesheets[%d]", i))...)
|
|
| 308 | 308 |
} |
| 309 | 309 |
|
| 310 | 310 |
nameTaken := map[string]bool{}
|
| 311 | 311 |
for i, extConfig := range config.Extensions {
|
| 312 | 312 |
extConfigErrors := ValidateAssetExtensionsConfig(extConfig).Prefix(fmt.Sprintf("extensions[%d]", i))
|
| 313 |
- allErrs = append(allErrs, extConfigErrors...) |
|
| 313 |
+ validationResults.AddErrors(extConfigErrors...) |
|
| 314 | 314 |
if nameTaken[extConfig.Name] {
|
| 315 | 315 |
dupError := fielderrors.NewFieldInvalid(fmt.Sprintf("extensions[%d].name", i), extConfig.Name, "duplicate extension name")
|
| 316 |
- allErrs = append(allErrs, dupError) |
|
| 316 |
+ validationResults.AddErrors(dupError) |
|
| 317 | 317 |
} else {
|
| 318 | 318 |
nameTaken[extConfig.Name] = true |
| 319 | 319 |
} |
| 320 | 320 |
} |
| 321 | 321 |
|
| 322 |
- return allErrs |
|
| 322 |
+ return validationResults |
|
| 323 | 323 |
} |
| 324 | 324 |
|
| 325 | 325 |
var extNameExp = regexp.MustCompile(`^[A-Za-z0-9_]+$`) |
| ... | ... |
@@ -11,45 +11,45 @@ import ( |
| 11 | 11 |
"github.com/openshift/origin/pkg/cmd/server/api" |
| 12 | 12 |
) |
| 13 | 13 |
|
| 14 |
-func ValidateNodeConfig(config *api.NodeConfig) fielderrors.ValidationErrorList {
|
|
| 15 |
- allErrs := fielderrors.ValidationErrorList{}
|
|
| 14 |
+func ValidateNodeConfig(config *api.NodeConfig) ValidationResults {
|
|
| 15 |
+ validationResults := ValidationResults{}
|
|
| 16 | 16 |
|
| 17 | 17 |
if len(config.NodeName) == 0 {
|
| 18 |
- allErrs = append(allErrs, fielderrors.NewFieldRequired("nodeName"))
|
|
| 18 |
+ validationResults.AddErrors(fielderrors.NewFieldRequired("nodeName"))
|
|
| 19 | 19 |
} |
| 20 | 20 |
if len(config.NodeIP) > 0 {
|
| 21 |
- allErrs = append(allErrs, ValidateSpecifiedIP(config.NodeIP, "nodeIP")...) |
|
| 21 |
+ validationResults.AddErrors(ValidateSpecifiedIP(config.NodeIP, "nodeIP")...) |
|
| 22 | 22 |
} |
| 23 | 23 |
|
| 24 |
- allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...)
|
|
| 24 |
+ validationResults.Append(ValidateServingInfo(config.ServingInfo).Prefix("servingInfo"))
|
|
| 25 | 25 |
if config.ServingInfo.BindNetwork == "tcp6" {
|
| 26 |
- allErrs = append(allErrs, fielderrors.NewFieldInvalid("servingInfo.bindNetwork", config.ServingInfo.BindNetwork, "tcp6 is not a valid bindNetwork for nodes, must be tcp or tcp4"))
|
|
| 26 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("servingInfo.bindNetwork", config.ServingInfo.BindNetwork, "tcp6 is not a valid bindNetwork for nodes, must be tcp or tcp4"))
|
|
| 27 | 27 |
} |
| 28 |
- allErrs = append(allErrs, ValidateKubeConfig(config.MasterKubeConfig, "masterKubeConfig")...) |
|
| 28 |
+ validationResults.AddErrors(ValidateKubeConfig(config.MasterKubeConfig, "masterKubeConfig")...) |
|
| 29 | 29 |
|
| 30 | 30 |
if len(config.DNSIP) > 0 {
|
| 31 |
- allErrs = append(allErrs, ValidateSpecifiedIP(config.DNSIP, "dnsIP")...) |
|
| 31 |
+ validationResults.AddErrors(ValidateSpecifiedIP(config.DNSIP, "dnsIP")...) |
|
| 32 | 32 |
} |
| 33 | 33 |
|
| 34 |
- allErrs = append(allErrs, ValidateImageConfig(config.ImageConfig).Prefix("imageConfig")...)
|
|
| 34 |
+ validationResults.AddErrors(ValidateImageConfig(config.ImageConfig).Prefix("imageConfig")...)
|
|
| 35 | 35 |
|
| 36 | 36 |
if config.PodManifestConfig != nil {
|
| 37 |
- allErrs = append(allErrs, ValidatePodManifestConfig(config.PodManifestConfig).Prefix("podManifestConfig")...)
|
|
| 37 |
+ validationResults.AddErrors(ValidatePodManifestConfig(config.PodManifestConfig).Prefix("podManifestConfig")...)
|
|
| 38 | 38 |
} |
| 39 | 39 |
|
| 40 |
- allErrs = append(allErrs, ValidateNetworkConfig(config.NetworkConfig).Prefix("networkConfig")...)
|
|
| 40 |
+ validationResults.AddErrors(ValidateNetworkConfig(config.NetworkConfig).Prefix("networkConfig")...)
|
|
| 41 | 41 |
|
| 42 |
- allErrs = append(allErrs, ValidateDockerConfig(config.DockerConfig).Prefix("dockerConfig")...)
|
|
| 42 |
+ validationResults.AddErrors(ValidateDockerConfig(config.DockerConfig).Prefix("dockerConfig")...)
|
|
| 43 | 43 |
|
| 44 |
- allErrs = append(allErrs, ValidateNodeAuthConfig(config.AuthConfig).Prefix("authConfig")...)
|
|
| 44 |
+ validationResults.AddErrors(ValidateNodeAuthConfig(config.AuthConfig).Prefix("authConfig")...)
|
|
| 45 | 45 |
|
| 46 |
- allErrs = append(allErrs, ValidateKubeletExtendedArguments(config.KubeletArguments).Prefix("kubeletArguments")...)
|
|
| 46 |
+ validationResults.AddErrors(ValidateKubeletExtendedArguments(config.KubeletArguments).Prefix("kubeletArguments")...)
|
|
| 47 | 47 |
|
| 48 | 48 |
if _, err := time.ParseDuration(config.IPTablesSyncPeriod); err != nil {
|
| 49 |
- allErrs = append(allErrs, fielderrors.NewFieldInvalid("iptablesSyncPeriod", config.IPTablesSyncPeriod, fmt.Sprintf("unable to parse iptablesSyncPeriod: %v. Examples with correct format: '5s', '1m', '2h22m'", err)))
|
|
| 49 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("iptablesSyncPeriod", config.IPTablesSyncPeriod, fmt.Sprintf("unable to parse iptablesSyncPeriod: %v. Examples with correct format: '5s', '1m', '2h22m'", err)))
|
|
| 50 | 50 |
} |
| 51 | 51 |
|
| 52 |
- return allErrs |
|
| 52 |
+ return validationResults |
|
| 53 | 53 |
} |
| 54 | 54 |
|
| 55 | 55 |
func ValidateNodeAuthConfig(config api.NodeAuthConfig) fielderrors.ValidationErrorList {
|
| ... | ... |
@@ -1,6 +1,8 @@ |
| 1 | 1 |
package validation |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "crypto/tls" |
|
| 5 |
+ "crypto/x509" |
|
| 4 | 6 |
"fmt" |
| 5 | 7 |
"net" |
| 6 | 8 |
"net/url" |
| ... | ... |
@@ -10,9 +12,12 @@ import ( |
| 10 | 10 |
"github.com/spf13/pflag" |
| 11 | 11 |
|
| 12 | 12 |
kvalidation "k8s.io/kubernetes/pkg/api/validation" |
| 13 |
+ kutil "k8s.io/kubernetes/pkg/util" |
|
| 13 | 14 |
"k8s.io/kubernetes/pkg/util/fielderrors" |
| 15 |
+ "k8s.io/kubernetes/pkg/util/sets" |
|
| 14 | 16 |
|
| 15 | 17 |
"github.com/openshift/origin/pkg/cmd/server/api" |
| 18 |
+ cmdutil "github.com/openshift/origin/pkg/cmd/util" |
|
| 16 | 19 |
cmdflags "github.com/openshift/origin/pkg/cmd/util/flags" |
| 17 | 20 |
) |
| 18 | 21 |
|
| ... | ... |
@@ -48,46 +53,124 @@ func ValidateCertInfo(certInfo api.CertInfo, required bool) fielderrors.Validati |
| 48 | 48 |
allErrs = append(allErrs, ValidateFile(certInfo.KeyFile, "keyFile")...) |
| 49 | 49 |
} |
| 50 | 50 |
|
| 51 |
+ // validate certfile/keyfile load/parse? |
|
| 52 |
+ |
|
| 51 | 53 |
return allErrs |
| 52 | 54 |
} |
| 53 | 55 |
|
| 54 |
-func ValidateServingInfo(info api.ServingInfo) fielderrors.ValidationErrorList {
|
|
| 55 |
- allErrs := fielderrors.ValidationErrorList{}
|
|
| 56 |
+func ValidateServingInfo(info api.ServingInfo) ValidationResults {
|
|
| 57 |
+ validationResults := ValidationResults{}
|
|
| 58 |
+ |
|
| 59 |
+ validationResults.AddErrors(ValidateHostPort(info.BindAddress, "bindAddress")...) |
|
| 60 |
+ validationResults.AddErrors(ValidateCertInfo(info.ServerCert, false)...) |
|
| 61 |
+ |
|
| 62 |
+ if len(info.NamedCertificates) > 0 && len(info.ServerCert.CertFile) == 0 {
|
|
| 63 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("namedCertificates", "", "a default certificate and key is required in certFile/keyFile in order to use namedCertificates"))
|
|
| 64 |
+ } |
|
| 56 | 65 |
|
| 57 |
- allErrs = append(allErrs, ValidateHostPort(info.BindAddress, "bindAddress")...) |
|
| 58 |
- allErrs = append(allErrs, ValidateCertInfo(info.ServerCert, false)...) |
|
| 66 |
+ validationResults.Append(ValidateNamedCertificates("namedCertificates", info.NamedCertificates))
|
|
| 59 | 67 |
|
| 60 | 68 |
switch info.BindNetwork {
|
| 61 | 69 |
case "tcp", "tcp4", "tcp6": |
| 62 | 70 |
default: |
| 63 |
- allErrs = append(allErrs, fielderrors.NewFieldInvalid("bindNetwork", info.BindNetwork, "must be 'tcp', 'tcp4', or 'tcp6'"))
|
|
| 71 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("bindNetwork", info.BindNetwork, "must be 'tcp', 'tcp4', or 'tcp6'"))
|
|
| 64 | 72 |
} |
| 65 | 73 |
|
| 66 | 74 |
if len(info.ServerCert.CertFile) > 0 {
|
| 67 | 75 |
if len(info.ClientCA) > 0 {
|
| 68 |
- allErrs = append(allErrs, ValidateFile(info.ClientCA, "clientCA")...) |
|
| 76 |
+ validationResults.AddErrors(ValidateFile(info.ClientCA, "clientCA")...) |
|
| 69 | 77 |
} |
| 70 | 78 |
} else {
|
| 71 | 79 |
if len(info.ClientCA) > 0 {
|
| 72 |
- allErrs = append(allErrs, fielderrors.NewFieldInvalid("clientCA", info.ClientCA, "cannot specify a clientCA without a certFile"))
|
|
| 80 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("clientCA", info.ClientCA, "cannot specify a clientCA without a certFile"))
|
|
| 73 | 81 |
} |
| 74 | 82 |
} |
| 75 | 83 |
|
| 76 |
- return allErrs |
|
| 84 |
+ return validationResults |
|
| 85 |
+} |
|
| 86 |
+ |
|
| 87 |
+func ValidateNamedCertificates(fieldName string, namedCertificates []api.NamedCertificate) ValidationResults {
|
|
| 88 |
+ validationResults := ValidationResults{}
|
|
| 89 |
+ |
|
| 90 |
+ takenNames := sets.NewString() |
|
| 91 |
+ for i, namedCertificate := range namedCertificates {
|
|
| 92 |
+ fieldName := fmt.Sprintf("%s[%d]", fieldName, i)
|
|
| 93 |
+ |
|
| 94 |
+ certDNSNames := []string{}
|
|
| 95 |
+ if len(namedCertificate.CertFile) == 0 {
|
|
| 96 |
+ validationResults.AddErrors(fielderrors.NewFieldRequired(fieldName + ".certInfo")) |
|
| 97 |
+ } else if certInfoErrors := ValidateCertInfo(namedCertificate.CertInfo, false); len(certInfoErrors) > 0 {
|
|
| 98 |
+ validationResults.AddErrors(certInfoErrors.Prefix(fieldName)...) |
|
| 99 |
+ } else if cert, err := tls.LoadX509KeyPair(namedCertificate.CertFile, namedCertificate.KeyFile); err != nil {
|
|
| 100 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid(fieldName+".certInfo", namedCertificate.CertInfo, fmt.Sprintf("error loading certificate/key: %v", err)))
|
|
| 101 |
+ } else {
|
|
| 102 |
+ leaf, _ := x509.ParseCertificate(cert.Certificate[0]) |
|
| 103 |
+ certDNSNames = append(certDNSNames, leaf.Subject.CommonName) |
|
| 104 |
+ certDNSNames = append(certDNSNames, leaf.DNSNames...) |
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ if len(namedCertificate.Names) == 0 {
|
|
| 108 |
+ validationResults.AddErrors(fielderrors.NewFieldRequired(fieldName + ".names")) |
|
| 109 |
+ } |
|
| 110 |
+ for j, name := range namedCertificate.Names {
|
|
| 111 |
+ nameFieldName := fieldName + fmt.Sprintf(".names[%d]", j)
|
|
| 112 |
+ if len(name) == 0 {
|
|
| 113 |
+ validationResults.AddErrors(fielderrors.NewFieldRequired(nameFieldName)) |
|
| 114 |
+ continue |
|
| 115 |
+ } |
|
| 116 |
+ |
|
| 117 |
+ if takenNames.Has(name) {
|
|
| 118 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid(nameFieldName, name, "this name is already used in another named certificate")) |
|
| 119 |
+ continue |
|
| 120 |
+ } |
|
| 121 |
+ |
|
| 122 |
+ // validate names as domain names or *.*.foo.com domain names |
|
| 123 |
+ validDNSName := true |
|
| 124 |
+ for _, s := range strings.Split(name, ".") {
|
|
| 125 |
+ if s != "*" && !kutil.IsDNS1123Label(s) {
|
|
| 126 |
+ validDNSName = false |
|
| 127 |
+ } |
|
| 128 |
+ } |
|
| 129 |
+ if !validDNSName {
|
|
| 130 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid(nameFieldName, name, "must be a valid DNS name")) |
|
| 131 |
+ continue |
|
| 132 |
+ } |
|
| 133 |
+ |
|
| 134 |
+ takenNames.Insert(name) |
|
| 135 |
+ |
|
| 136 |
+ // validate certificate has common name or subject alt names that match |
|
| 137 |
+ if len(certDNSNames) > 0 {
|
|
| 138 |
+ foundMatch := false |
|
| 139 |
+ for _, dnsName := range certDNSNames {
|
|
| 140 |
+ if cmdutil.HostnameMatches(dnsName, name) {
|
|
| 141 |
+ foundMatch = true |
|
| 142 |
+ break |
|
| 143 |
+ } |
|
| 144 |
+ } |
|
| 145 |
+ if !foundMatch {
|
|
| 146 |
+ validationResults.AddWarnings(fielderrors.NewFieldInvalid(nameFieldName, name, "the specified certificate does not have a CommonName or DNS subjectAltName that matches this name")) |
|
| 147 |
+ } |
|
| 148 |
+ } |
|
| 149 |
+ } |
|
| 150 |
+ } |
|
| 151 |
+ |
|
| 152 |
+ return validationResults |
|
| 77 | 153 |
} |
| 78 | 154 |
|
| 79 |
-func ValidateHTTPServingInfo(info api.HTTPServingInfo) fielderrors.ValidationErrorList {
|
|
| 80 |
- allErrs := ValidateServingInfo(info.ServingInfo) |
|
| 155 |
+func ValidateHTTPServingInfo(info api.HTTPServingInfo) ValidationResults {
|
|
| 156 |
+ validationResults := ValidationResults{}
|
|
| 157 |
+ |
|
| 158 |
+ validationResults.Append(ValidateServingInfo(info.ServingInfo)) |
|
| 81 | 159 |
|
| 82 | 160 |
if info.MaxRequestsInFlight < 0 {
|
| 83 |
- allErrs = append(allErrs, fielderrors.NewFieldInvalid("maxRequestsInFlight", info.MaxRequestsInFlight, "must be zero (no limit) or greater"))
|
|
| 161 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("maxRequestsInFlight", info.MaxRequestsInFlight, "must be zero (no limit) or greater"))
|
|
| 84 | 162 |
} |
| 85 | 163 |
|
| 86 | 164 |
if info.RequestTimeoutSeconds < -1 {
|
| 87 |
- allErrs = append(allErrs, fielderrors.NewFieldInvalid("requestTimeoutSeconds", info.RequestTimeoutSeconds, "must be -1 (no timeout), 0 (default timeout), or greater"))
|
|
| 165 |
+ validationResults.AddErrors(fielderrors.NewFieldInvalid("requestTimeoutSeconds", info.RequestTimeoutSeconds, "must be -1 (no timeout), 0 (default timeout), or greater"))
|
|
| 88 | 166 |
} |
| 89 | 167 |
|
| 90 |
- return allErrs |
|
| 168 |
+ return validationResults |
|
| 91 | 169 |
} |
| 92 | 170 |
|
| 93 | 171 |
func ValidateDisabledFeatures(disabledFeatures []string, field string) fielderrors.ValidationErrorList {
|
| 94 | 172 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,208 @@ |
| 0 |
+package validation |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io/ioutil" |
|
| 4 |
+ "os" |
|
| 5 |
+ "strings" |
|
| 6 |
+ "testing" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/openshift/origin/pkg/cmd/server/api" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func TestValidateServingInfo(t *testing.T) {
|
|
| 12 |
+ certFile, err := ioutil.TempFile("", "cert.crt")
|
|
| 13 |
+ if err != nil {
|
|
| 14 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 15 |
+ } |
|
| 16 |
+ defer os.Remove(certFile.Name()) |
|
| 17 |
+ certFileName := certFile.Name() |
|
| 18 |
+ ioutil.WriteFile(certFile.Name(), localhostCert, os.FileMode(0755)) |
|
| 19 |
+ |
|
| 20 |
+ keyFile, err := ioutil.TempFile("", "cert.key")
|
|
| 21 |
+ if err != nil {
|
|
| 22 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 23 |
+ } |
|
| 24 |
+ defer os.Remove(keyFile.Name()) |
|
| 25 |
+ keyFileName := keyFile.Name() |
|
| 26 |
+ ioutil.WriteFile(keyFile.Name(), localhostKey, os.FileMode(0755)) |
|
| 27 |
+ |
|
| 28 |
+ testcases := map[string]struct {
|
|
| 29 |
+ ServingInfo api.ServingInfo |
|
| 30 |
+ ExpectedErrors []string |
|
| 31 |
+ ExpectedWarnings []string |
|
| 32 |
+ }{
|
|
| 33 |
+ "basic": {
|
|
| 34 |
+ ServingInfo: api.ServingInfo{
|
|
| 35 |
+ BindAddress: "0.0.0.0:1234", |
|
| 36 |
+ BindNetwork: "tcp", |
|
| 37 |
+ }, |
|
| 38 |
+ }, |
|
| 39 |
+ "missing key": {
|
|
| 40 |
+ ServingInfo: api.ServingInfo{
|
|
| 41 |
+ BindAddress: "0.0.0.0:1234", |
|
| 42 |
+ BindNetwork: "tcp", |
|
| 43 |
+ ServerCert: api.CertInfo{
|
|
| 44 |
+ CertFile: certFileName, |
|
| 45 |
+ }, |
|
| 46 |
+ }, |
|
| 47 |
+ ExpectedErrors: []string{"keyFile: required"},
|
|
| 48 |
+ }, |
|
| 49 |
+ |
|
| 50 |
+ "namedCertificates valid": {
|
|
| 51 |
+ ServingInfo: api.ServingInfo{
|
|
| 52 |
+ BindAddress: "0.0.0.0:1234", |
|
| 53 |
+ BindNetwork: "tcp", |
|
| 54 |
+ ServerCert: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName},
|
|
| 55 |
+ NamedCertificates: []api.NamedCertificate{
|
|
| 56 |
+ {Names: []string{"example.com"}, CertInfo: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName}},
|
|
| 57 |
+ }, |
|
| 58 |
+ }, |
|
| 59 |
+ }, |
|
| 60 |
+ |
|
| 61 |
+ "namedCertificates without default cert": {
|
|
| 62 |
+ ServingInfo: api.ServingInfo{
|
|
| 63 |
+ BindAddress: "0.0.0.0:1234", |
|
| 64 |
+ BindNetwork: "tcp", |
|
| 65 |
+ //ServerCert: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName},
|
|
| 66 |
+ NamedCertificates: []api.NamedCertificate{
|
|
| 67 |
+ {Names: []string{"example.com"}, CertInfo: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName}},
|
|
| 68 |
+ }, |
|
| 69 |
+ }, |
|
| 70 |
+ ExpectedErrors: []string{"namedCertificates: invalid"},
|
|
| 71 |
+ }, |
|
| 72 |
+ |
|
| 73 |
+ "namedCertificates with missing names": {
|
|
| 74 |
+ ServingInfo: api.ServingInfo{
|
|
| 75 |
+ BindAddress: "0.0.0.0:1234", |
|
| 76 |
+ BindNetwork: "tcp", |
|
| 77 |
+ ServerCert: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName},
|
|
| 78 |
+ NamedCertificates: []api.NamedCertificate{
|
|
| 79 |
+ {Names: []string{ /*"example.com"*/ }, CertInfo: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName}},
|
|
| 80 |
+ }, |
|
| 81 |
+ }, |
|
| 82 |
+ ExpectedErrors: []string{"namedCertificates[0].names: required"},
|
|
| 83 |
+ }, |
|
| 84 |
+ "namedCertificates with missing key": {
|
|
| 85 |
+ ServingInfo: api.ServingInfo{
|
|
| 86 |
+ BindAddress: "0.0.0.0:1234", |
|
| 87 |
+ BindNetwork: "tcp", |
|
| 88 |
+ ServerCert: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName},
|
|
| 89 |
+ NamedCertificates: []api.NamedCertificate{
|
|
| 90 |
+ {Names: []string{"example.com"}, CertInfo: api.CertInfo{CertFile: certFileName /*, KeyFile: keyFileName*/}},
|
|
| 91 |
+ }, |
|
| 92 |
+ }, |
|
| 93 |
+ ExpectedErrors: []string{"namedCertificates[0].keyFile: required"},
|
|
| 94 |
+ }, |
|
| 95 |
+ "namedCertificates with duplicate names": {
|
|
| 96 |
+ ServingInfo: api.ServingInfo{
|
|
| 97 |
+ BindAddress: "0.0.0.0:1234", |
|
| 98 |
+ BindNetwork: "tcp", |
|
| 99 |
+ ServerCert: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName},
|
|
| 100 |
+ NamedCertificates: []api.NamedCertificate{
|
|
| 101 |
+ {Names: []string{"example.com"}, CertInfo: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName}},
|
|
| 102 |
+ {Names: []string{"example.com"}, CertInfo: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName}},
|
|
| 103 |
+ }, |
|
| 104 |
+ }, |
|
| 105 |
+ ExpectedErrors: []string{"namedCertificates[1].names[0]: invalid"},
|
|
| 106 |
+ }, |
|
| 107 |
+ "namedCertificates with empty name": {
|
|
| 108 |
+ ServingInfo: api.ServingInfo{
|
|
| 109 |
+ BindAddress: "0.0.0.0:1234", |
|
| 110 |
+ BindNetwork: "tcp", |
|
| 111 |
+ ServerCert: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName},
|
|
| 112 |
+ NamedCertificates: []api.NamedCertificate{
|
|
| 113 |
+ {Names: []string{""}, CertInfo: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName}},
|
|
| 114 |
+ }, |
|
| 115 |
+ }, |
|
| 116 |
+ ExpectedErrors: []string{"namedCertificates[0].names[0]: required"},
|
|
| 117 |
+ }, |
|
| 118 |
+ |
|
| 119 |
+ "namedCertificates with unmatched DNS name": {
|
|
| 120 |
+ ServingInfo: api.ServingInfo{
|
|
| 121 |
+ BindAddress: "0.0.0.0:1234", |
|
| 122 |
+ BindNetwork: "tcp", |
|
| 123 |
+ ServerCert: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName},
|
|
| 124 |
+ NamedCertificates: []api.NamedCertificate{
|
|
| 125 |
+ {Names: []string{"badexample.com"}, CertInfo: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName}},
|
|
| 126 |
+ }, |
|
| 127 |
+ }, |
|
| 128 |
+ ExpectedWarnings: []string{"namedCertificates[0].names[0]: invalid"},
|
|
| 129 |
+ }, |
|
| 130 |
+ "namedCertificates with non-DNS names": {
|
|
| 131 |
+ ServingInfo: api.ServingInfo{
|
|
| 132 |
+ BindAddress: "0.0.0.0:1234", |
|
| 133 |
+ BindNetwork: "tcp", |
|
| 134 |
+ ServerCert: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName},
|
|
| 135 |
+ NamedCertificates: []api.NamedCertificate{
|
|
| 136 |
+ {Names: []string{"foo bar.com"}, CertInfo: api.CertInfo{CertFile: certFileName, KeyFile: keyFileName}},
|
|
| 137 |
+ }, |
|
| 138 |
+ }, |
|
| 139 |
+ ExpectedErrors: []string{
|
|
| 140 |
+ "namedCertificates[0].names[0]: invalid value 'foo bar.com', Details: must be a valid DNS name", |
|
| 141 |
+ }, |
|
| 142 |
+ }, |
|
| 143 |
+ } |
|
| 144 |
+ |
|
| 145 |
+ for k, tc := range testcases {
|
|
| 146 |
+ result := ValidateServingInfo(tc.ServingInfo) |
|
| 147 |
+ |
|
| 148 |
+ if len(tc.ExpectedErrors) != len(result.Errors) {
|
|
| 149 |
+ t.Errorf("%s: Expected %d errors, got %d", k, len(tc.ExpectedErrors), len(result.Errors))
|
|
| 150 |
+ for _, e := range tc.ExpectedErrors {
|
|
| 151 |
+ t.Logf("\tExpected error: %s", e)
|
|
| 152 |
+ } |
|
| 153 |
+ for _, r := range result.Errors {
|
|
| 154 |
+ t.Logf("\tActual error: %s", r.Error())
|
|
| 155 |
+ } |
|
| 156 |
+ continue |
|
| 157 |
+ } |
|
| 158 |
+ for i, r := range result.Errors {
|
|
| 159 |
+ if !strings.Contains(r.Error(), tc.ExpectedErrors[i]) {
|
|
| 160 |
+ t.Errorf("%s: Expected error containing %s, got %s", k, tc.ExpectedErrors[i], r.Error())
|
|
| 161 |
+ } |
|
| 162 |
+ } |
|
| 163 |
+ |
|
| 164 |
+ if len(tc.ExpectedWarnings) != len(result.Warnings) {
|
|
| 165 |
+ t.Errorf("%s: Expected %d warning, got %d", k, len(tc.ExpectedWarnings), len(result.Warnings))
|
|
| 166 |
+ for _, e := range tc.ExpectedErrors {
|
|
| 167 |
+ t.Logf("\tExpected warning: %s", e)
|
|
| 168 |
+ } |
|
| 169 |
+ for _, r := range result.Warnings {
|
|
| 170 |
+ t.Logf("\tActual warning: %s", r.Error())
|
|
| 171 |
+ } |
|
| 172 |
+ continue |
|
| 173 |
+ } |
|
| 174 |
+ for i, r := range result.Warnings {
|
|
| 175 |
+ if !strings.Contains(r.Error(), tc.ExpectedWarnings[i]) {
|
|
| 176 |
+ t.Errorf("%s: Expected warning containing %s, got %s", k, tc.ExpectedWarnings[i], r.Error())
|
|
| 177 |
+ } |
|
| 178 |
+ } |
|
| 179 |
+ } |
|
| 180 |
+} |
|
| 181 |
+ |
|
| 182 |
+// localhostCert is a PEM-encoded TLS cert with SAN IPs |
|
| 183 |
+// "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end |
|
| 184 |
+// of ASN.1 time). |
|
| 185 |
+// generated from src/crypto/tls: |
|
| 186 |
+// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h |
|
| 187 |
+var localhostCert = []byte(`-----BEGIN CERTIFICATE----- |
|
| 188 |
+MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD |
|
| 189 |
+bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj |
|
| 190 |
+bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa |
|
| 191 |
+IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA |
|
| 192 |
+AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud |
|
| 193 |
+EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA |
|
| 194 |
+AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk |
|
| 195 |
+Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA== |
|
| 196 |
+-----END CERTIFICATE-----`) |
|
| 197 |
+ |
|
| 198 |
+// localhostKey is the private key for localhostCert. |
|
| 199 |
+var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- |
|
| 200 |
+MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0 |
|
| 201 |
+0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV |
|
| 202 |
+NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d |
|
| 203 |
+AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW |
|
| 204 |
+MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD |
|
| 205 |
+EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA |
|
| 206 |
+1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE= |
|
| 207 |
+-----END RSA PRIVATE KEY-----`) |
| ... | ... |
@@ -190,6 +190,10 @@ func BuildKubernetesNodeConfig(options configapi.NodeConfig) (*NodeConfig, error |
| 190 | 190 |
|
| 191 | 191 |
// TODO: could be cleaner |
| 192 | 192 |
if configapi.UseTLS(options.ServingInfo) {
|
| 193 |
+ extraCerts, err := configapi.GetNamedCertificateMap(options.ServingInfo.NamedCertificates) |
|
| 194 |
+ if err != nil {
|
|
| 195 |
+ return nil, err |
|
| 196 |
+ } |
|
| 193 | 197 |
cfg.TLSOptions = &kubelet.TLSOptions{
|
| 194 | 198 |
Config: &tls.Config{
|
| 195 | 199 |
// Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability) |
| ... | ... |
@@ -198,6 +202,10 @@ func BuildKubernetesNodeConfig(options configapi.NodeConfig) (*NodeConfig, error |
| 198 | 198 |
// Verification is done by the authn layer |
| 199 | 199 |
ClientAuth: tls.RequestClientCert, |
| 200 | 200 |
ClientCAs: clientCAs, |
| 201 |
+ // Set SNI certificate func |
|
| 202 |
+ // Do not use NameToCertificate, since that requires certificates be included in the server's tlsConfig.Certificates list, |
|
| 203 |
+ // which we do not control when running with http.Server#ListenAndServeTLS |
|
| 204 |
+ GetCertificate: cmdutil.GetCertificateFunc(extraCerts), |
|
| 201 | 205 |
}, |
| 202 | 206 |
CertFile: options.ServingInfo.ServerCert.CertFile, |
| 203 | 207 |
KeyFile: options.ServingInfo.ServerCert.KeyFile, |
| ... | ... |
@@ -81,9 +81,15 @@ func (c *AssetConfig) Run() {
|
| 81 | 81 |
|
| 82 | 82 |
go util.Forever(func() {
|
| 83 | 83 |
if isTLS {
|
| 84 |
+ extraCerts, err := configapi.GetNamedCertificateMap(c.Options.ServingInfo.NamedCertificates) |
|
| 85 |
+ if err != nil {
|
|
| 86 |
+ glog.Fatal(err) |
|
| 87 |
+ } |
|
| 84 | 88 |
server.TLSConfig = &tls.Config{
|
| 85 | 89 |
// Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability) |
| 86 | 90 |
MinVersion: tls.VersionTLS10, |
| 91 |
+ // Set SNI certificate func |
|
| 92 |
+ GetCertificate: cmdutil.GetCertificateFunc(extraCerts), |
|
| 87 | 93 |
} |
| 88 | 94 |
glog.Infof("Web console listening at https://%s", c.Options.ServingInfo.BindAddress)
|
| 89 | 95 |
glog.Fatal(cmdutil.ListenAndServeTLS(server, c.Options.ServingInfo.BindNetwork, c.Options.ServingInfo.ServerCert.CertFile, c.Options.ServingInfo.ServerCert.KeyFile)) |
| ... | ... |
@@ -230,6 +230,10 @@ func (c *MasterConfig) serve(handler http.Handler, extra []string) {
|
| 230 | 230 |
glog.Infof(s, c.Options.ServingInfo.BindAddress) |
| 231 | 231 |
} |
| 232 | 232 |
if c.TLS {
|
| 233 |
+ extraCerts, err := configapi.GetNamedCertificateMap(c.Options.ServingInfo.NamedCertificates) |
|
| 234 |
+ if err != nil {
|
|
| 235 |
+ glog.Fatal(err) |
|
| 236 |
+ } |
|
| 233 | 237 |
server.TLSConfig = &tls.Config{
|
| 234 | 238 |
// Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability) |
| 235 | 239 |
MinVersion: tls.VersionTLS10, |
| ... | ... |
@@ -237,6 +241,8 @@ func (c *MasterConfig) serve(handler http.Handler, extra []string) {
|
| 237 | 237 |
// This allows certificates to be validated by authenticators, while still allowing other auth types |
| 238 | 238 |
ClientAuth: tls.RequestClientCert, |
| 239 | 239 |
ClientCAs: c.ClientCAs, |
| 240 |
+ // Set SNI certificate func |
|
| 241 |
+ GetCertificate: cmdutil.GetCertificateFunc(extraCerts), |
|
| 240 | 242 |
} |
| 241 | 243 |
glog.Fatal(cmdutil.ListenAndServeTLS(server, c.Options.ServingInfo.BindNetwork, c.Options.ServingInfo.ServerCert.CertFile, c.Options.ServingInfo.ServerCert.KeyFile)) |
| 242 | 244 |
} else {
|
| ... | ... |
@@ -163,9 +163,14 @@ func (o NodeOptions) RunNode() error {
|
| 163 | 163 |
return err |
| 164 | 164 |
} |
| 165 | 165 |
|
| 166 |
- errs := validation.ValidateNodeConfig(nodeConfig) |
|
| 167 |
- if len(errs) != 0 {
|
|
| 168 |
- return kerrors.NewInvalid("NodeConfig", o.ConfigFile, errs)
|
|
| 166 |
+ validationResults := validation.ValidateNodeConfig(nodeConfig) |
|
| 167 |
+ if len(validationResults.Warnings) != 0 {
|
|
| 168 |
+ for _, warning := range validationResults.Warnings {
|
|
| 169 |
+ glog.Warningf("%v", warning)
|
|
| 170 |
+ } |
|
| 171 |
+ } |
|
| 172 |
+ if len(validationResults.Errors) != 0 {
|
|
| 173 |
+ return kerrors.NewInvalid("NodeConfig", o.ConfigFile, validationResults.Errors)
|
|
| 169 | 174 |
} |
| 170 | 175 |
|
| 171 | 176 |
_, kubeClientConfig, err := configapi.GetKubeClient(nodeConfig.MasterKubeConfig) |
| ... | ... |
@@ -6,8 +6,11 @@ import ( |
| 6 | 6 |
"fmt" |
| 7 | 7 |
"net" |
| 8 | 8 |
"net/http" |
| 9 |
+ "strings" |
|
| 9 | 10 |
"time" |
| 10 | 11 |
|
| 12 |
+ "k8s.io/kubernetes/pkg/util/sets" |
|
| 13 |
+ |
|
| 11 | 14 |
"github.com/golang/glog" |
| 12 | 15 |
) |
| 13 | 16 |
|
| ... | ... |
@@ -141,3 +144,56 @@ func TransportFor(ca string, certFile string, keyFile string) (http.RoundTripper |
| 141 | 141 |
|
| 142 | 142 |
return &transport, nil |
| 143 | 143 |
} |
| 144 |
+ |
|
| 145 |
+// GetCertificateFunc returns a function that can be used in tls.Config#GetCertificate |
|
| 146 |
+// Returns nil if len(certs) == 0 |
|
| 147 |
+func GetCertificateFunc(certs map[string]*tls.Certificate) func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
| 148 |
+ if len(certs) == 0 {
|
|
| 149 |
+ return nil |
|
| 150 |
+ } |
|
| 151 |
+ // Replica of tls.Config#getCertificate logic |
|
| 152 |
+ return func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
| 153 |
+ if clientHello == nil {
|
|
| 154 |
+ return nil, nil |
|
| 155 |
+ } |
|
| 156 |
+ |
|
| 157 |
+ name := clientHello.ServerName |
|
| 158 |
+ name = strings.ToLower(name) |
|
| 159 |
+ name = strings.TrimRight(name, ".") |
|
| 160 |
+ for _, candidate := range HostnameMatchSpecCandidates(name) {
|
|
| 161 |
+ if cert, ok := certs[candidate]; ok {
|
|
| 162 |
+ return cert, nil |
|
| 163 |
+ } |
|
| 164 |
+ } |
|
| 165 |
+ return nil, nil |
|
| 166 |
+ } |
|
| 167 |
+} |
|
| 168 |
+ |
|
| 169 |
+// HostnameMatchSpecCandidates returns a list of match specs that would match the provided hostname |
|
| 170 |
+// Returns nil if len(hostname) == 0 |
|
| 171 |
+func HostnameMatchSpecCandidates(hostname string) []string {
|
|
| 172 |
+ if len(hostname) == 0 {
|
|
| 173 |
+ return nil |
|
| 174 |
+ } |
|
| 175 |
+ |
|
| 176 |
+ // Exact match has priority |
|
| 177 |
+ candidates := []string{hostname}
|
|
| 178 |
+ |
|
| 179 |
+ // Replace successive labels in the name with wildcards, to require an exact match on number of |
|
| 180 |
+ // path segments, because certificates cannot wildcard multiple levels of subdomains |
|
| 181 |
+ // |
|
| 182 |
+ // This is primarily to be consistent with tls.Config#getCertificate implementation |
|
| 183 |
+ // |
|
| 184 |
+ // It using a cert signed for *.foo.example.com and *.bar.example.com by specifying the name *.*.example.com |
|
| 185 |
+ labels := strings.Split(hostname, ".") |
|
| 186 |
+ for i := range labels {
|
|
| 187 |
+ labels[i] = "*" |
|
| 188 |
+ candidates = append(candidates, strings.Join(labels, ".")) |
|
| 189 |
+ } |
|
| 190 |
+ return candidates |
|
| 191 |
+} |
|
| 192 |
+ |
|
| 193 |
+// HostnameMatches returns true if the given hostname is matched by the given matchSpec |
|
| 194 |
+func HostnameMatches(hostname string, matchSpec string) bool {
|
|
| 195 |
+ return sets.NewString(HostnameMatchSpecCandidates(hostname)...).Has(matchSpec) |
|
| 196 |
+} |
| 144 | 197 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,75 @@ |
| 0 |
+package util |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "reflect" |
|
| 4 |
+ "testing" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+func TestHostnameMatchSpecCandidates(t *testing.T) {
|
|
| 8 |
+ testcases := []struct {
|
|
| 9 |
+ Hostname string |
|
| 10 |
+ ExpectedSpecs []string |
|
| 11 |
+ }{
|
|
| 12 |
+ {
|
|
| 13 |
+ Hostname: "", |
|
| 14 |
+ ExpectedSpecs: nil, |
|
| 15 |
+ }, |
|
| 16 |
+ {
|
|
| 17 |
+ Hostname: "a", |
|
| 18 |
+ ExpectedSpecs: []string{"a", "*"},
|
|
| 19 |
+ }, |
|
| 20 |
+ {
|
|
| 21 |
+ Hostname: "foo.bar", |
|
| 22 |
+ ExpectedSpecs: []string{"foo.bar", "*.bar", "*.*"},
|
|
| 23 |
+ }, |
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ for _, tc := range testcases {
|
|
| 27 |
+ specs := HostnameMatchSpecCandidates(tc.Hostname) |
|
| 28 |
+ if !reflect.DeepEqual(specs, tc.ExpectedSpecs) {
|
|
| 29 |
+ t.Errorf("%s: Expected %#v, got %#v", tc.Hostname, tc.ExpectedSpecs, specs)
|
|
| 30 |
+ } |
|
| 31 |
+ } |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+func TestHostnameMatches(t *testing.T) {
|
|
| 35 |
+ testcases := []struct {
|
|
| 36 |
+ Hostname string |
|
| 37 |
+ Spec string |
|
| 38 |
+ ExpectedMatch bool |
|
| 39 |
+ }{
|
|
| 40 |
+ // Empty hostname matches nothing |
|
| 41 |
+ {Hostname: "", Spec: "", ExpectedMatch: false},
|
|
| 42 |
+ |
|
| 43 |
+ // Empty spec matches nothing |
|
| 44 |
+ {Hostname: "a", Spec: "", ExpectedMatch: false},
|
|
| 45 |
+ |
|
| 46 |
+ // Exact match |
|
| 47 |
+ {Hostname: "a", Spec: "a", ExpectedMatch: true},
|
|
| 48 |
+ // Single segment wildcard match |
|
| 49 |
+ {Hostname: "a", Spec: "*", ExpectedMatch: true},
|
|
| 50 |
+ |
|
| 51 |
+ // Mismatched segment count should not match |
|
| 52 |
+ {Hostname: "a", Spec: "*.a", ExpectedMatch: false},
|
|
| 53 |
+ {Hostname: "a", Spec: "*.*", ExpectedMatch: false},
|
|
| 54 |
+ |
|
| 55 |
+ // Exact match, multi-segment |
|
| 56 |
+ {Hostname: "a.b", Spec: "a.b", ExpectedMatch: true},
|
|
| 57 |
+ // Wildcard subdomain match |
|
| 58 |
+ {Hostname: "a.b", Spec: "*.b", ExpectedMatch: true},
|
|
| 59 |
+ // Multi-level wildcard match |
|
| 60 |
+ {Hostname: "a.b", Spec: "*.*", ExpectedMatch: true},
|
|
| 61 |
+ |
|
| 62 |
+ // Only subdomain wildcards are allowed |
|
| 63 |
+ {Hostname: "a.b", Spec: "a.*", ExpectedMatch: false},
|
|
| 64 |
+ // Mismatched segment count should not match |
|
| 65 |
+ {Hostname: "a.b", Spec: "*.a.b", ExpectedMatch: false},
|
|
| 66 |
+ } |
|
| 67 |
+ |
|
| 68 |
+ for i, tc := range testcases {
|
|
| 69 |
+ matches := HostnameMatches(tc.Hostname, tc.Spec) |
|
| 70 |
+ if matches != tc.ExpectedMatch {
|
|
| 71 |
+ t.Errorf("%d: Expected match=%v, got %v (hostname=%s, specs=%v)", i, tc.ExpectedMatch, matches, tc.Hostname, tc.Spec)
|
|
| 72 |
+ } |
|
| 73 |
+ } |
|
| 74 |
+} |
| ... | ... |
@@ -41,8 +41,12 @@ func (d NodeConfigCheck) Check() types.DiagnosticResult {
|
| 41 | 41 |
|
| 42 | 42 |
r.Info("DH1003", fmt.Sprintf("Found a node config file: %[1]s", d.NodeConfigFile))
|
| 43 | 43 |
|
| 44 |
- for _, err := range configvalidation.ValidateNodeConfig(nodeConfig) {
|
|
| 44 |
+ results := configvalidation.ValidateNodeConfig(nodeConfig) |
|
| 45 |
+ for _, err := range results.Errors {
|
|
| 45 | 46 |
r.Error("DH1004", err, fmt.Sprintf("Validation of node config file '%s' failed:\n(%T) %[2]v", d.NodeConfigFile, err))
|
| 46 | 47 |
} |
| 48 |
+ for _, err := range results.Warnings {
|
|
| 49 |
+ r.Warn("DH1005", err, fmt.Sprintf("Validation of node config file '%s' warning:\n(%T) %[2]v", d.NodeConfigFile, err))
|
|
| 50 |
+ } |
|
| 47 | 51 |
return r |
| 48 | 52 |
} |
| 49 | 53 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,306 @@ |
| 0 |
+// +build integration,etcd |
|
| 1 |
+ |
|
| 2 |
+package integration |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "crypto/tls" |
|
| 6 |
+ "io/ioutil" |
|
| 7 |
+ "net" |
|
| 8 |
+ "net/http" |
|
| 9 |
+ "net/url" |
|
| 10 |
+ "os" |
|
| 11 |
+ "testing" |
|
| 12 |
+ |
|
| 13 |
+ configapi "github.com/openshift/origin/pkg/cmd/server/api" |
|
| 14 |
+ "github.com/openshift/origin/pkg/cmd/util" |
|
| 15 |
+ testserver "github.com/openshift/origin/test/util/server" |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+const ( |
|
| 19 |
+ // oadm ca create-signer-cert --cert=sni-ca.crt --key=sni-ca.key --name=sni-signer --serial=sni-serial.txt |
|
| 20 |
+ sniCACert = "sni-ca.crt" |
|
| 21 |
+ |
|
| 22 |
+ // oadm ca create-server-cert --cert=sni.crt --key=sni.key --hostnames=127.0.0.1,customhost.com,*.wildcardhost.com --signer-cert=sni-ca.crt --signer-key=sni-ca.key --signer-serial=sni-serial.txt |
|
| 23 |
+ sniServerCert = "sni-server.crt" |
|
| 24 |
+ sniServerKey = "sni-server.key" |
|
| 25 |
+) |
|
| 26 |
+ |
|
| 27 |
+func TestSNI(t *testing.T) {
|
|
| 28 |
+ // Create tempfiles with certs and keys we're going to use |
|
| 29 |
+ certNames := map[string]string{}
|
|
| 30 |
+ for certName, certContents := range sniCerts {
|
|
| 31 |
+ f, err := ioutil.TempFile("", certName)
|
|
| 32 |
+ if err != nil {
|
|
| 33 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 34 |
+ } |
|
| 35 |
+ defer os.Remove(f.Name()) |
|
| 36 |
+ if err := ioutil.WriteFile(f.Name(), certContents, os.FileMode(0600)); err != nil {
|
|
| 37 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 38 |
+ } |
|
| 39 |
+ certNames[certName] = f.Name() |
|
| 40 |
+ } |
|
| 41 |
+ |
|
| 42 |
+ // Build master config |
|
| 43 |
+ masterOptions, err := testserver.DefaultMasterOptions() |
|
| 44 |
+ if err != nil {
|
|
| 45 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 46 |
+ } |
|
| 47 |
+ |
|
| 48 |
+ // Set custom cert |
|
| 49 |
+ masterOptions.ServingInfo.NamedCertificates = []configapi.NamedCertificate{
|
|
| 50 |
+ {
|
|
| 51 |
+ Names: []string{"customhost.com"},
|
|
| 52 |
+ CertInfo: configapi.CertInfo{
|
|
| 53 |
+ CertFile: certNames[sniServerCert], |
|
| 54 |
+ KeyFile: certNames[sniServerKey], |
|
| 55 |
+ }, |
|
| 56 |
+ }, |
|
| 57 |
+ {
|
|
| 58 |
+ Names: []string{"*.wildcardhost.com"},
|
|
| 59 |
+ CertInfo: configapi.CertInfo{
|
|
| 60 |
+ CertFile: certNames[sniServerCert], |
|
| 61 |
+ KeyFile: certNames[sniServerKey], |
|
| 62 |
+ }, |
|
| 63 |
+ }, |
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 66 |
+ // Start server |
|
| 67 |
+ _, err = testserver.StartConfiguredMaster(masterOptions) |
|
| 68 |
+ if err != nil {
|
|
| 69 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 70 |
+ } |
|
| 71 |
+ |
|
| 72 |
+ // Build transports |
|
| 73 |
+ sniRoots, err := util.CertPoolFromFile(certNames[sniCACert]) |
|
| 74 |
+ if err != nil {
|
|
| 75 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 76 |
+ } |
|
| 77 |
+ sniConfig := &tls.Config{RootCAs: sniRoots}
|
|
| 78 |
+ |
|
| 79 |
+ generatedRoots, err := util.CertPoolFromFile(masterOptions.ServiceAccountConfig.MasterCA) |
|
| 80 |
+ if err != nil {
|
|
| 81 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 82 |
+ } |
|
| 83 |
+ generatedConfig := &tls.Config{RootCAs: generatedRoots}
|
|
| 84 |
+ |
|
| 85 |
+ insecureConfig := &tls.Config{InsecureSkipVerify: true}
|
|
| 86 |
+ |
|
| 87 |
+ tests := map[string]struct {
|
|
| 88 |
+ Hostname string |
|
| 89 |
+ TLSConfig *tls.Config |
|
| 90 |
+ ExpectedOK bool |
|
| 91 |
+ }{
|
|
| 92 |
+ "sni client -> generated ip": {
|
|
| 93 |
+ Hostname: "127.0.0.1", |
|
| 94 |
+ TLSConfig: sniConfig, |
|
| 95 |
+ }, |
|
| 96 |
+ "sni client -> generated hostname": {
|
|
| 97 |
+ Hostname: "openshift", |
|
| 98 |
+ TLSConfig: sniConfig, |
|
| 99 |
+ }, |
|
| 100 |
+ "sni client -> sni host": {
|
|
| 101 |
+ Hostname: "customhost.com", |
|
| 102 |
+ TLSConfig: sniConfig, |
|
| 103 |
+ ExpectedOK: true, |
|
| 104 |
+ }, |
|
| 105 |
+ "sni client -> sni wildcard host": {
|
|
| 106 |
+ Hostname: "www.wildcardhost.com", |
|
| 107 |
+ TLSConfig: sniConfig, |
|
| 108 |
+ ExpectedOK: true, |
|
| 109 |
+ }, |
|
| 110 |
+ "sni client -> invalid ip": {
|
|
| 111 |
+ Hostname: "10.10.10.10", |
|
| 112 |
+ TLSConfig: sniConfig, |
|
| 113 |
+ }, |
|
| 114 |
+ "sni client -> invalid host": {
|
|
| 115 |
+ Hostname: "invalidhost.com", |
|
| 116 |
+ TLSConfig: sniConfig, |
|
| 117 |
+ }, |
|
| 118 |
+ |
|
| 119 |
+ "generated client -> generated ip": {
|
|
| 120 |
+ Hostname: "127.0.0.1", |
|
| 121 |
+ TLSConfig: generatedConfig, |
|
| 122 |
+ ExpectedOK: true, |
|
| 123 |
+ }, |
|
| 124 |
+ "generated client -> generated hostname": {
|
|
| 125 |
+ Hostname: "openshift", |
|
| 126 |
+ TLSConfig: generatedConfig, |
|
| 127 |
+ ExpectedOK: true, |
|
| 128 |
+ }, |
|
| 129 |
+ "generated client -> sni host": {
|
|
| 130 |
+ Hostname: "customhost.com", |
|
| 131 |
+ TLSConfig: generatedConfig, |
|
| 132 |
+ }, |
|
| 133 |
+ "generated client -> sni wildcard host": {
|
|
| 134 |
+ Hostname: "www.wildcardhost.com", |
|
| 135 |
+ TLSConfig: generatedConfig, |
|
| 136 |
+ }, |
|
| 137 |
+ "generated client -> invalid ip": {
|
|
| 138 |
+ Hostname: "10.10.10.10", |
|
| 139 |
+ TLSConfig: generatedConfig, |
|
| 140 |
+ }, |
|
| 141 |
+ "generated client -> invalid host": {
|
|
| 142 |
+ Hostname: "invalidhost.com", |
|
| 143 |
+ TLSConfig: generatedConfig, |
|
| 144 |
+ }, |
|
| 145 |
+ |
|
| 146 |
+ "insecure client -> generated ip": {
|
|
| 147 |
+ Hostname: "127.0.0.1", |
|
| 148 |
+ TLSConfig: insecureConfig, |
|
| 149 |
+ ExpectedOK: true, |
|
| 150 |
+ }, |
|
| 151 |
+ "insecure client -> generated hostname": {
|
|
| 152 |
+ Hostname: "openshift", |
|
| 153 |
+ TLSConfig: insecureConfig, |
|
| 154 |
+ ExpectedOK: true, |
|
| 155 |
+ }, |
|
| 156 |
+ "insecure client -> sni host": {
|
|
| 157 |
+ Hostname: "customhost.com", |
|
| 158 |
+ TLSConfig: insecureConfig, |
|
| 159 |
+ ExpectedOK: true, |
|
| 160 |
+ }, |
|
| 161 |
+ "insecure client -> sni wildcard host": {
|
|
| 162 |
+ Hostname: "www.wildcardhost.com", |
|
| 163 |
+ TLSConfig: insecureConfig, |
|
| 164 |
+ ExpectedOK: true, |
|
| 165 |
+ }, |
|
| 166 |
+ "insecure client -> invalid ip": {
|
|
| 167 |
+ Hostname: "10.10.10.10", |
|
| 168 |
+ TLSConfig: insecureConfig, |
|
| 169 |
+ ExpectedOK: true, |
|
| 170 |
+ }, |
|
| 171 |
+ "insecure client -> invalid host": {
|
|
| 172 |
+ Hostname: "invalidhost.com", |
|
| 173 |
+ TLSConfig: insecureConfig, |
|
| 174 |
+ ExpectedOK: true, |
|
| 175 |
+ }, |
|
| 176 |
+ } |
|
| 177 |
+ |
|
| 178 |
+ masterPublicURL, err := url.Parse(masterOptions.MasterPublicURL) |
|
| 179 |
+ if err != nil {
|
|
| 180 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 181 |
+ } |
|
| 182 |
+ |
|
| 183 |
+ for k, tc := range tests {
|
|
| 184 |
+ u := *masterPublicURL |
|
| 185 |
+ if err != nil {
|
|
| 186 |
+ t.Errorf("%s: unexpected error: %v", k, err)
|
|
| 187 |
+ continue |
|
| 188 |
+ } |
|
| 189 |
+ u.Path = "/healthz" |
|
| 190 |
+ |
|
| 191 |
+ if _, port, err := net.SplitHostPort(u.Host); err == nil {
|
|
| 192 |
+ u.Host = net.JoinHostPort(tc.Hostname, port) |
|
| 193 |
+ } else {
|
|
| 194 |
+ u.Host = tc.Hostname |
|
| 195 |
+ } |
|
| 196 |
+ |
|
| 197 |
+ req, err := http.NewRequest("GET", u.String(), nil)
|
|
| 198 |
+ if err != nil {
|
|
| 199 |
+ t.Errorf("%s: unexpected error: %v", k, err)
|
|
| 200 |
+ continue |
|
| 201 |
+ } |
|
| 202 |
+ |
|
| 203 |
+ transport := &http.Transport{
|
|
| 204 |
+ // Custom Dial func to always dial the real master, no matter what host is asked for |
|
| 205 |
+ Dial: func(network, addr string) (net.Conn, error) {
|
|
| 206 |
+ // t.Logf("%s: Dialing for %s", k, addr)
|
|
| 207 |
+ return net.Dial(network, masterPublicURL.Host) |
|
| 208 |
+ }, |
|
| 209 |
+ TLSClientConfig: tc.TLSConfig, |
|
| 210 |
+ } |
|
| 211 |
+ resp, err := transport.RoundTrip(req) |
|
| 212 |
+ if tc.ExpectedOK && err != nil {
|
|
| 213 |
+ t.Errorf("%s: unexpected error: %v", k, err)
|
|
| 214 |
+ continue |
|
| 215 |
+ } |
|
| 216 |
+ if !tc.ExpectedOK && err == nil {
|
|
| 217 |
+ t.Errorf("%s: expected error, got none", k)
|
|
| 218 |
+ continue |
|
| 219 |
+ } |
|
| 220 |
+ if err == nil {
|
|
| 221 |
+ data, err := ioutil.ReadAll(resp.Body) |
|
| 222 |
+ if err != nil {
|
|
| 223 |
+ t.Errorf("%s: unexpected error: %v", k, err)
|
|
| 224 |
+ continue |
|
| 225 |
+ } |
|
| 226 |
+ if string(data) != "ok" {
|
|
| 227 |
+ t.Errorf("%s: expected %q, got %q", k, "ok", string(data))
|
|
| 228 |
+ continue |
|
| 229 |
+ } |
|
| 230 |
+ } |
|
| 231 |
+ } |
|
| 232 |
+} |
|
| 233 |
+ |
|
| 234 |
+var ( |
|
| 235 |
+ sniCerts = map[string][]byte{
|
|
| 236 |
+ sniCACert: []byte(`-----BEGIN CERTIFICATE----- |
|
| 237 |
+MIICxjCCAbCgAwIBAgIBATALBgkqhkiG9w0BAQswFTETMBEGA1UEAxMKc25pLXNp |
|
| 238 |
+Z25lcjAgFw0xNTEwMTMwNTEyMzFaGA8yMDY1MDkzMDA1MTIzMlowFTETMBEGA1UE |
|
| 239 |
+AxMKc25pLXNpZ25lcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOb |
|
| 240 |
+OG1vBlq8UdJZJbumpiSEZbC7Jkyh4IceLmRvojGlMPgyoT67PedZAOD4EAbMVh+h |
|
| 241 |
+tAYZgcTC8ASCA1UelXFPng5OwurQv1uBdJTLiTBzPusDenWaCZc/zf2EJEM03WZ9 |
|
| 242 |
+k4VgYecYkOmSZqvDj5Dl4lvEsVUL9RBaNMzgbzXsZE1+brlUKR+UmF2yX56vBj2R |
|
| 243 |
+WiQHOIQgjYLaAciHJsZVkqznoN155vPggX791l62fC5Ungil6TQTPdcizBR+deLN |
|
| 244 |
+S+HFxH0+YjEPiTb8PdoSUH+W3Y6d2zSzebm1GZUOKtgOQTXhmIuB3GGwOSDLC9Rq |
|
| 245 |
+6a1z3HTZNBMQ7Y8NOJsCAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgCkMA8GA1UdEwEB |
|
| 246 |
+/wQFMAMBAf8wCwYJKoZIhvcNAQELA4IBAQCQ2laesgvXmT6EKXvnASbKPt35Lr26 |
|
| 247 |
+Jp0mayAGhJgf17WzQnmN0IFyZyu0H81TdIydximxKX6KWMrTk4z/7CbUa07AwVCy |
|
| 248 |
+zBecwL0ajIgGakKqiiH3EJKrlg4jNKNOKboMuouNoROrwc5UkWfjSATFkjTTShDO |
|
| 249 |
+Qd8JrAEBtEBaXr0Xueb5rdlrR/j7UMEpjUT7bGUxnhgF/h1TJ6cIiRpVKpA8NxyL |
|
| 250 |
+ZBkouK3hPEeu92K7U/NBBE//YRQz6EghixQSv/ZEGmlsU8z6g8ay+d9iZa5DhYsh |
|
| 251 |
+/IYxG0ykvGUH9d1AWplmHAqPwrcWSym49cZEmiHx/tO/wRp6+51lyfcn |
|
| 252 |
+-----END CERTIFICATE----- |
|
| 253 |
+`), |
|
| 254 |
+ |
|
| 255 |
+ sniServerCert: []byte(`-----BEGIN CERTIFICATE----- |
|
| 256 |
+MIIDIDCCAgqgAwIBAgIBAzALBgkqhkiG9w0BAQswFTETMBEGA1UEAxMKc25pLXNp |
|
| 257 |
+Z25lcjAgFw0xNTEwMTMwNTMzMTVaGA8yMDY1MDkzMDA1MzMxNlowHTEbMBkGA1UE |
|
| 258 |
+AxMSKi53aWxkY2FyZGhvc3QuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB |
|
| 259 |
+CgKCAQEA2hh1F9DXwkCDLi20i4ehoL40DhHFPsVfHa4/nIvmcOgJE6qZCh2oH+H8 |
|
| 260 |
+vDLQg4wNNpa47le5AuYqupXcXeTtrGq/AwXSnrC3LVJYqleAnVgLoL5+Y2NEj8Hx |
|
| 261 |
+fzdWGhkCMDtA1QdZeq8HpCv3ZziRUxiZ/ddI4rZjFsCDoZUAhGGDzHCqkKbsuBI8 |
|
| 262 |
+bNkz9V0FfXn8OprfRCUPtMJrHusNsWCHrQ4ceRYOzsa9y4IVQnmvcpMlh7qu7kiO |
|
| 263 |
+AbbFm41J8W/BEMxIBwJOITB2qAgkOKxF48IpsDeCWbOJ2qedisR5PNl/te4Qv/Kt |
|
| 264 |
+D6FTqpIHv/cSkn9fz2ji6Jy574oR7wIDAQABo3UwczAOBgNVHQ8BAf8EBAMCAKAw |
|
| 265 |
+EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADA+BgNVHREENzA1ghIq |
|
| 266 |
+LndpbGRjYXJkaG9zdC5jb22CDmN1c3RvbWhvc3QuY29tggkxMjcuMC4wLjGHBH8A |
|
| 267 |
+AAEwCwYJKoZIhvcNAQELA4IBAQB5Q7Pbx9jBP566XgjQGnpoNK5jJf3CqkjBKSdG |
|
| 268 |
+OdoumjDEx2Ast3h1edXBVnU0DPxbfxMo3lBIAiJ+sNWGErYlIDVdpglFVyYIn+V6 |
|
| 269 |
+71gUDaA+1rXBS3f0QEQ9pOh3b4qSWbmYr9mbYRQus1cFYq+KTsLmzuNGvRwSvvE7 |
|
| 270 |
+5nSTgUozXTF4fSyWGGTcy13ZFg6mLlMoivjVswUJsi2nLf/yejnwdJGs4ZR06qCx |
|
| 271 |
+dYB2LaUCbI6AQlaQMVrxsVXTfUkstKF5cuPKq/MenBcH88j3uqj8BF6YjR1wRfHc |
|
| 272 |
+p0NuWIRsuXBHEr6o3iQ3KlmQLWfLeS0K+FkN60mwDTteIzuR |
|
| 273 |
+-----END CERTIFICATE----- |
|
| 274 |
+`), |
|
| 275 |
+ |
|
| 276 |
+ sniServerKey: []byte(`-----BEGIN RSA PRIVATE KEY----- |
|
| 277 |
+MIIEpQIBAAKCAQEA2hh1F9DXwkCDLi20i4ehoL40DhHFPsVfHa4/nIvmcOgJE6qZ |
|
| 278 |
+Ch2oH+H8vDLQg4wNNpa47le5AuYqupXcXeTtrGq/AwXSnrC3LVJYqleAnVgLoL5+ |
|
| 279 |
+Y2NEj8HxfzdWGhkCMDtA1QdZeq8HpCv3ZziRUxiZ/ddI4rZjFsCDoZUAhGGDzHCq |
|
| 280 |
+kKbsuBI8bNkz9V0FfXn8OprfRCUPtMJrHusNsWCHrQ4ceRYOzsa9y4IVQnmvcpMl |
|
| 281 |
+h7qu7kiOAbbFm41J8W/BEMxIBwJOITB2qAgkOKxF48IpsDeCWbOJ2qedisR5PNl/ |
|
| 282 |
+te4Qv/KtD6FTqpIHv/cSkn9fz2ji6Jy574oR7wIDAQABAoIBAQDTq2UJpkGhYGdw |
|
| 283 |
+zB8sRIjTr4ZqGUkscPatoc5PK2COOEWG9s3tiXcA6p4WMeM5qRWx43q8qBsB+02B |
|
| 284 |
+Ja1o26To7/lO/7m5Fp3RuNghCyfije9LJVcZMuD5/StbYuOIFLmRAhEcMDPh5Dow |
|
| 285 |
+VhOZ9MbmtTvPp8AveQCWtmWKz0hfMVLGUP91yqbXD/7h7/VpN+kKEOwoOUFqBshZ |
|
| 286 |
+ziJs261VnI1UYeDg6jokZTDD9t1vS0EbPoe8NozPt8zF4bihXpLX7MCjJO32ISQl |
|
| 287 |
+4cjH+94Wl1/X2MerG0RA8BTMlMSACiQdyeE7C4d/4tfRFp2EHqSBgwsTMLAJKy1P |
|
| 288 |
+1NjejLmxAoGBAOxx1i22W/J1tqNvO7we3G8nuojRG5f69u1YKrW/yhk89a3hmyQ6 |
|
| 289 |
+MB15xz55CyTEl+FqPQReMJlmBsbKqHl8viOWhRc12t4i/JsYgroCcbIHME86FXuH |
|
| 290 |
+boNJo0DB0Q1xoCQEuNiygyv3qLq5tKPBk0lAn2Sxhyt398MDukOcZ8ujAoGBAOwi |
|
| 291 |
+HuM/d6A78F6l1NHJdOoTFvLXCbM3mWSdfoU/UxQmEk9Wr3kfudOu2ZAsWfiznjw4 |
|
| 292 |
+jgN/JS/WTY+NvVEXMIASz3QoNmLFSN0c5DCBSBVW/1B9rFP9GLCDrZMspluYg/gR |
|
| 293 |
+8MLxC6AAVBcbNZj7Z16mmAyUs3LCJmP9P/7GcgVFAoGANesrvVbllt/zG0gFZjvf |
|
| 294 |
+ZtW3evW8hibr4moFq1amHqVBHTriZxuB12bq4bs2qFbQj83rRjC4gnK6vuB+FN42 |
|
| 295 |
+eeUcSpO0ao2t7yxiu0pNZRywjpCfT4Et2XCUcvL/2kH8E9qj0H683OzoJFSu9dzx |
|
| 296 |
+2nWLI6o8OdRswqL5+esT3GMCgYEApYnmDXnY60QZ5sBqygdpJw/q7qNB8Znwt1CR |
|
| 297 |
++efC3kUyYNxsd4V+SKAzdZciG/AP5jfflyPzde3Oweyj481V+vM07EGknumfgyNV |
|
| 298 |
+9YssdYlfw5XW0aqFPHmTnbGXjm8FVUt+dat2ctzIFsrEcFMOzJQN1AQLKVBiiYZo |
|
| 299 |
+7rtAA+ECgYEAoR685udqJrCnvhL911cT+7/DrUyNLFmYvoIMlfG9DRXmKTIlyFH9 |
|
| 300 |
+A+TGxO02VaWYxm/zFTNIkXsEpNrxW4CVZtWM6biktT20S0p11IA1x8SCGqOimlF8 |
|
| 301 |
+Yg4OZRDUUYRAl0MUaslTyIxBfEVal4XgvBwhjXk0BMP6OJNDHWT3mUY= |
|
| 302 |
+-----END RSA PRIVATE KEY----- |
|
| 303 |
+`), |
|
| 304 |
+ } |
|
| 305 |
+) |