From 078996d887030946bdf73f3eb22f08a20051e878 Mon Sep 17 00:00:00 2001 From: Aiden Keating Date: Wed, 7 Feb 2018 17:03:46 +0000 Subject: [PATCH 1/9] Include certificate pinning hashes in the service configuration This change allows the SDKs to consume hashes used for certificate pinning. This means that the end user does not have to configure certificate pinning manually or using a IDE to have pinning with their provisioned services. --- pkg/cmd/clientConfig.go | 4 ++++ pkg/cmd/convert.go | 29 ++++++++++++++++++++++++++++- pkg/cmd/types.go | 11 ++++++----- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/pkg/cmd/clientConfig.go b/pkg/cmd/clientConfig.go index b001bce..ef89b74 100644 --- a/pkg/cmd/clientConfig.go +++ b/pkg/cmd/clientConfig.go @@ -120,6 +120,10 @@ kubectl plugin mobile get clientconfig`, } } if includedService { + err = appendCertificatePinningInfoToService(svcConfig) + if err != nil { + return errors.Wrap(err, "unable to append certificate pinning information to service config") + } ret = append(ret, svcConfig) } } diff --git a/pkg/cmd/convert.go b/pkg/cmd/convert.go index ccfa02e..27b5df2 100644 --- a/pkg/cmd/convert.go +++ b/pkg/cmd/convert.go @@ -16,14 +16,40 @@ package cmd import ( "strings" - "k8s.io/client-go/pkg/api/v1" + "crypto/sha256" + "crypto/tls" + "encoding/base64" + "net/url" ) func isClientConfigKey(key string) bool { return key == "url" || key == "name" || key == "type" || key == "id" } +func appendCertificatePinningInfoToService(s *ServiceConfig) error { + serviceURL, err := url.Parse(s.URL) + if err != nil { + return err + } + if serviceURL.Scheme != "https" { + return nil + } + // TODO: Make the InsecureSkipVerify here configurable. I think there will be times when we don't want to allow auto-pinning to unverified certificates. + // TODO: Allow for the Host variable to contain a port. So split it and then if there's a port use that, else use 443. + conn, err := tls.Dial("tcp", serviceURL.Host+":443", &tls.Config{ + InsecureSkipVerify: true, + }) + if err != nil { + return err + } + hasher := sha256.New() + // TODO: Do we want to loop through here? The command here https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning only returns what we are currently returning. + hasher.Write(conn.ConnectionState().PeerCertificates[0].RawSubjectPublicKeyInfo) + s.CertificatePinningHashes = []string{base64.StdEncoding.EncodeToString(hasher.Sum(nil))} + return nil +} + func convertSecretToMobileService(s v1.Secret) *Service { params := map[string]string{} for key, value := range s.Data { @@ -32,6 +58,7 @@ func convertSecretToMobileService(s v1.Secret) *Service { } } external := s.Labels["external"] == "true" + return &Service{ Namespace: s.Labels["namespace"], ID: s.Name, diff --git a/pkg/cmd/types.go b/pkg/cmd/types.go index f3d413a..0b898e8 100644 --- a/pkg/cmd/types.go +++ b/pkg/cmd/types.go @@ -80,11 +80,12 @@ type ServiceConfigs struct { //ServiceConfig is the configuration for a specific service type ServiceConfig struct { - ID string `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - URL string `json:"url"` - Config map[string]interface{} `json:"config"` + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + URL string `json:"url"` + Config map[string]interface{} `json:"config"` + CertificatePinningHashes []string `json:"certificatePinningHashes"` } // defaultSecretConvertor will provide a default secret to config conversion From 1370024a2e3a08a6cc0315addf4312e9afe23f95 Mon Sep 17 00:00:00 2001 From: Aiden Keating Date: Sun, 11 Feb 2018 16:32:15 +0000 Subject: [PATCH 2/9] Use environment variable to allow invalid/self-signed certificates Currently we allow all invalid/self-signed certificates. We need an option to allow this to be disallowed. This comes in the form of an environment variable named AEROGEAR_ALLOW_INVALID_CERTS. Now, by default, the CLI will not allow any invalid certificates to be used. Only when the environment variable is set to 'true' will they be allowed. --- pkg/cmd/clientConfig_test.go | 6 ++++-- pkg/cmd/convert.go | 28 ++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/pkg/cmd/clientConfig_test.go b/pkg/cmd/clientConfig_test.go index e2b7f29..7a7ff8f 100644 --- a/pkg/cmd/clientConfig_test.go +++ b/pkg/cmd/clientConfig_test.go @@ -203,14 +203,16 @@ func TestClientConfigCmd_GetClientConfigCmd(t *testing.T) { "name": "test-service", "type": "", "url": "", - "config": {} + "config": {}, + "certificatePinningHashes": null }, { "id": "keycloak", "name": "keycloak", "type": "", "url": "", - "config": {} + "config": {}, + "certificatePinningHashes": null } ] }` diff --git a/pkg/cmd/convert.go b/pkg/cmd/convert.go index 27b5df2..431f1a8 100644 --- a/pkg/cmd/convert.go +++ b/pkg/cmd/convert.go @@ -19,14 +19,26 @@ import ( "k8s.io/client-go/pkg/api/v1" "crypto/sha256" "crypto/tls" + "crypto/x509" "encoding/base64" "net/url" + "os" ) func isClientConfigKey(key string) bool { return key == "url" || key == "name" || key == "type" || key == "id" } +func retrieveCertificateForURL(url *url.URL, allowInvalidCertificates bool) (*x509.Certificate, error) { + conn, err := tls.Dial("tcp", url.Host+":443", &tls.Config{ + InsecureSkipVerify: allowInvalidCertificates, + }) + if err != nil { + return nil, err + } + return conn.ConnectionState().PeerCertificates[0], nil +} + func appendCertificatePinningInfoToService(s *ServiceConfig) error { serviceURL, err := url.Parse(s.URL) if err != nil { @@ -35,17 +47,17 @@ func appendCertificatePinningInfoToService(s *ServiceConfig) error { if serviceURL.Scheme != "https" { return nil } - // TODO: Make the InsecureSkipVerify here configurable. I think there will be times when we don't want to allow auto-pinning to unverified certificates. + + allowInvalidCertificates := os.Getenv("AEROGEAR_ALLOW_INVALID_CERTS") == "true" + // TODO: Allow for the Host variable to contain a port. So split it and then if there's a port use that, else use 443. - conn, err := tls.Dial("tcp", serviceURL.Host+":443", &tls.Config{ - InsecureSkipVerify: true, - }) - if err != nil { - return err - } + certificate, err := retrieveCertificateForURL(serviceURL, allowInvalidCertificates) hasher := sha256.New() // TODO: Do we want to loop through here? The command here https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning only returns what we are currently returning. - hasher.Write(conn.ConnectionState().PeerCertificates[0].RawSubjectPublicKeyInfo) + _, err = hasher.Write(certificate.RawSubjectPublicKeyInfo) + if err != nil { + return nil + } s.CertificatePinningHashes = []string{base64.StdEncoding.EncodeToString(hasher.Sum(nil))} return nil } From 2a8d917dfc9ef045d80cb0d31e166d1711b7f06f Mon Sep 17 00:00:00 2001 From: Aiden Keating Date: Mon, 12 Mar 2018 12:55:00 +0000 Subject: [PATCH 3/9] Add https key to client config --- glide.lock | 29 ++++++++++++++++-- glide.yaml | 4 ++- pkg/cmd/clientConfig.go | 9 +++--- pkg/cmd/clientConfig_test.go | 15 +++++----- pkg/cmd/convert.go | 58 ++++++++++++++++++++++++++---------- pkg/cmd/types.go | 27 ++++++++++------- 6 files changed, 100 insertions(+), 42 deletions(-) diff --git a/glide.lock b/glide.lock index 411a2ee..3f5b67a 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 29054621ae23155f331dc3fdc5890b6ff6cb277e751ace8e4acbb2f04cdce0d8 -updated: 2017-12-08T11:28:37.930876Z +hash: 81e00abfb782294c27574d500d9f15cdaff488722a9fdca8d7ed2ed4179a912a +updated: 2018-03-12T11:41:53.909954Z imports: - name: github.com/davecgh/go-spew version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d @@ -41,6 +41,8 @@ imports: version: 44145f04b68cf362d9c4df2182967c2275eaefed - name: github.com/google/gofuzz version: 44d81051d367757e1c7c6a5a86423ece9afcf63c +- name: github.com/goreleaser/goreleaser + version: 7778a494991ef46d865b3e75520fab7bcf9fbdfd - name: github.com/hashicorp/hcl version: d8c773c4cba11b11539e3d45f93daeaa5dcf1fa1 subpackages: @@ -71,7 +73,7 @@ imports: - jlexer - jwriter - name: github.com/mattn/go-runewidth - version: 97311d9f7767e3d6f422ea06661bc2c7a19e8a5d + version: 9e777a8366cce605130a531d2cd6363d07ad7317 - name: github.com/mitchellh/mapstructure version: 06020f85339e21b2478f756a78e295255ffa4d6a - name: github.com/olekukonko/tablewriter @@ -199,27 +201,48 @@ imports: - discovery - discovery/fake - kubernetes + - kubernetes/fake - kubernetes/scheme - kubernetes/typed/admissionregistration/v1alpha1 + - kubernetes/typed/admissionregistration/v1alpha1/fake - kubernetes/typed/apps/v1beta1 + - kubernetes/typed/apps/v1beta1/fake - kubernetes/typed/authentication/v1 + - kubernetes/typed/authentication/v1/fake - kubernetes/typed/authentication/v1beta1 + - kubernetes/typed/authentication/v1beta1/fake - kubernetes/typed/authorization/v1 + - kubernetes/typed/authorization/v1/fake - kubernetes/typed/authorization/v1beta1 + - kubernetes/typed/authorization/v1beta1/fake - kubernetes/typed/autoscaling/v1 + - kubernetes/typed/autoscaling/v1/fake - kubernetes/typed/autoscaling/v2alpha1 + - kubernetes/typed/autoscaling/v2alpha1/fake - kubernetes/typed/batch/v1 + - kubernetes/typed/batch/v1/fake - kubernetes/typed/batch/v2alpha1 + - kubernetes/typed/batch/v2alpha1/fake - kubernetes/typed/certificates/v1beta1 + - kubernetes/typed/certificates/v1beta1/fake - kubernetes/typed/core/v1 + - kubernetes/typed/core/v1/fake - kubernetes/typed/extensions/v1beta1 + - kubernetes/typed/extensions/v1beta1/fake - kubernetes/typed/networking/v1 + - kubernetes/typed/networking/v1/fake - kubernetes/typed/policy/v1beta1 + - kubernetes/typed/policy/v1beta1/fake - kubernetes/typed/rbac/v1alpha1 + - kubernetes/typed/rbac/v1alpha1/fake - kubernetes/typed/rbac/v1beta1 + - kubernetes/typed/rbac/v1beta1/fake - kubernetes/typed/settings/v1alpha1 + - kubernetes/typed/settings/v1alpha1/fake - kubernetes/typed/storage/v1 + - kubernetes/typed/storage/v1/fake - kubernetes/typed/storage/v1beta1 + - kubernetes/typed/storage/v1beta1/fake - pkg/api - pkg/api/v1 - pkg/api/v1/ref diff --git a/glide.yaml b/glide.yaml index b9b3e6f..00b705c 100644 --- a/glide.yaml +++ b/glide.yaml @@ -39,4 +39,6 @@ import: - package: github.com/olekukonko/tablewriter version: 65fec0d89a572b4367094e2058d3ebe667de3b60 - package: github.com/mattn/go-runewidth - version: ~0.0.2 \ No newline at end of file + version: ~0.0.2 +- package: github.com/goreleaser/goreleaser + version: v0.58.0 diff --git a/pkg/cmd/clientConfig.go b/pkg/cmd/clientConfig.go index ef89b74..4b81358 100644 --- a/pkg/cmd/clientConfig.go +++ b/pkg/cmd/clientConfig.go @@ -120,13 +120,13 @@ kubectl plugin mobile get clientconfig`, } } if includedService { - err = appendCertificatePinningInfoToService(svcConfig) - if err != nil { - return errors.Wrap(err, "unable to append certificate pinning information to service config") - } ret = append(ret, svcConfig) } } + servicePinningHashes, err := retrieveHTTPSConfigForServices(ret) + if err != nil { + return errors.Wrap(err, "unable to append https configuration for services.") + } outputJSON := ServiceConfigs{ Version: 1, @@ -134,6 +134,7 @@ kubectl plugin mobile get clientconfig`, Namespace: ns, ClientID: clientID, ClusterName: ccc.clusterHost, + Https: servicePinningHashes, } if err := ccc.Out.Render("get"+cmd.Name(), outputType(cmd.Flags()), outputJSON); err != nil { return errors.Wrap(err, fmt.Sprintf(output.FailedToOutPutInFormat, "ServiceConfig", outputType(cmd.Flags()))) diff --git a/pkg/cmd/clientConfig_test.go b/pkg/cmd/clientConfig_test.go index 7a7ff8f..1d5fd4e 100644 --- a/pkg/cmd/clientConfig_test.go +++ b/pkg/cmd/clientConfig_test.go @@ -115,7 +115,8 @@ func TestClientConfigCmd_GetClientConfigCmd(t *testing.T) { "clusterName": "test", "namespace": "testing-ns", "clientId": "client-id", - "services": [] + "services": [], + "https": [] }` if strings.TrimSpace(out.String()) != expected { return errors.New(fmt.Sprintf("expected: '%v', got: '%v'", expected, strings.TrimSpace(out.String()))) @@ -203,18 +204,17 @@ func TestClientConfigCmd_GetClientConfigCmd(t *testing.T) { "name": "test-service", "type": "", "url": "", - "config": {}, - "certificatePinningHashes": null + "config": {} }, { "id": "keycloak", "name": "keycloak", "type": "", "url": "", - "config": {}, - "certificatePinningHashes": null + "config": {} } - ] + ], + "https": [] }` if strings.TrimSpace(out.String()) != expected { return errors.New(fmt.Sprintf("expected: '%v', got: '%v'", expected, strings.TrimSpace(out.String()))) @@ -325,7 +325,8 @@ func TestClientConfigCmd_GetClientConfigCmd(t *testing.T) { "url": "", "config": {} } - ] + ], + "https": [] }` if strings.TrimSpace(out.String()) != expected { return errors.New(fmt.Sprintf("expected: '%v', got: '%v'", expected, strings.TrimSpace(out.String()))) diff --git a/pkg/cmd/convert.go b/pkg/cmd/convert.go index 431f1a8..a58db81 100644 --- a/pkg/cmd/convert.go +++ b/pkg/cmd/convert.go @@ -21,6 +21,8 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" + "fmt" + "github.com/pkg/errors" "net/url" "os" ) @@ -29,37 +31,61 @@ func isClientConfigKey(key string) bool { return key == "url" || key == "name" || key == "type" || key == "id" } -func retrieveCertificateForURL(url *url.URL, allowInvalidCertificates bool) (*x509.Certificate, error) { - conn, err := tls.Dial("tcp", url.Host+":443", &tls.Config{ - InsecureSkipVerify: allowInvalidCertificates, - }) - if err != nil { - return nil, err +func retrieveHTTPSConfigForServices(services []*ServiceConfig) ([]*CertificatePinningHash, error) { + httpsConfig := []*CertificatePinningHash{} + for _, svc := range services { + pinningHash, err := retrieveHTTPSConfigForService(svc) + if err != nil { + return nil, err + } + if pinningHash != nil { + httpsConfig = append(httpsConfig, pinningHash) + } } - return conn.ConnectionState().PeerCertificates[0], nil + return httpsConfig, nil } -func appendCertificatePinningInfoToService(s *ServiceConfig) error { - serviceURL, err := url.Parse(s.URL) +func retrieveHTTPSConfigForService(service *ServiceConfig) (*CertificatePinningHash, error) { + serviceURL, err := url.Parse(service.URL) if err != nil { - return err + return nil, err } if serviceURL.Scheme != "https" { - return nil + return nil, nil } allowInvalidCertificates := os.Getenv("AEROGEAR_ALLOW_INVALID_CERTS") == "true" - // TODO: Allow for the Host variable to contain a port. So split it and then if there's a port use that, else use 443. certificate, err := retrieveCertificateForURL(serviceURL, allowInvalidCertificates) + if err != nil { + return nil, err + } + hasher := sha256.New() - // TODO: Do we want to loop through here? The command here https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning only returns what we are currently returning. _, err = hasher.Write(certificate.RawSubjectPublicKeyInfo) if err != nil { - return nil + return nil, err + } + pinningHash := base64.StdEncoding.EncodeToString(hasher.Sum(nil)) + return &CertificatePinningHash{serviceURL.String(), pinningHash}, nil +} + +func retrieveCertificateForURL(url *url.URL, allowInvalidCertificates bool) (*x509.Certificate, error) { + // If the 443 port is not appended to the URLs host then we should append it. + port := "443" + if url.Port() != "" { + port = url.Port() + } + hostURL := fmt.Sprintf("%s:%s", url.Host, port) + + conn, err := tls.Dial("tcp", hostURL, &tls.Config{ + InsecureSkipVerify: allowInvalidCertificates, + }) + + if err != nil { + return nil, errors.Wrap(err, "Could not retrieve certificate for URL "+url.String()) } - s.CertificatePinningHashes = []string{base64.StdEncoding.EncodeToString(hasher.Sum(nil))} - return nil + return conn.ConnectionState().PeerCertificates[0], nil } func convertSecretToMobileService(s v1.Secret) *Service { diff --git a/pkg/cmd/types.go b/pkg/cmd/types.go index 0b898e8..988dec8 100644 --- a/pkg/cmd/types.go +++ b/pkg/cmd/types.go @@ -71,21 +71,26 @@ type SecretConvertor interface { //ServiceConfigs are collection of configurations for services in a specific namespace type ServiceConfigs struct { - Version int `json:"version"` - ClusterName string `json:"clusterName"` - Namespace string `json:"namespace"` - ClientID string `json:"clientId,omitempty"` - Services []*ServiceConfig `json:"services"` + Version int `json:"version"` + ClusterName string `json:"clusterName"` + Namespace string `json:"namespace"` + ClientID string `json:"clientId,omitempty"` + Services []*ServiceConfig `json:"services"` + Https []*CertificatePinningHash `json:"https"` } //ServiceConfig is the configuration for a specific service type ServiceConfig struct { - ID string `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - URL string `json:"url"` - Config map[string]interface{} `json:"config"` - CertificatePinningHashes []string `json:"certificatePinningHashes"` + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + URL string `json:"url"` + Config map[string]interface{} `json:"config"` +} + +type CertificatePinningHash struct { + Host string `json:"host"` + CertificateHash string `json:"certificateHash"` } // defaultSecretConvertor will provide a default secret to config conversion From f1b29b943546a6e14c9b644a0755283c4b5e10be Mon Sep 17 00:00:00 2001 From: Aiden Keating Date: Wed, 14 Mar 2018 10:54:28 +0000 Subject: [PATCH 4/9] Use correct format for https host --- pkg/cmd/clientConfig.go | 21 ++++++++++++++++----- pkg/cmd/clientConfig_test.go | 9 +++------ pkg/cmd/convert.go | 32 +++++++++++++++----------------- pkg/cmd/types.go | 2 +- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/pkg/cmd/clientConfig.go b/pkg/cmd/clientConfig.go index 4b81358..c2a61fa 100644 --- a/pkg/cmd/clientConfig.go +++ b/pkg/cmd/clientConfig.go @@ -53,6 +53,9 @@ func NewClientConfigCmd(k8Client kubernetes.Interface, mobileClient mobile.Inter // GetClientConfigCmd returns a cobra command object for getting client configs func (ccc *ClientConfigCmd) GetClientConfigCmd() *cobra.Command { + var includeCertificateHashes bool + var allowInvalidCertificateHashes bool + cmd := &cobra.Command{ Use: "clientconfig ", Short: "get clientconfig returns a client ready filtered configuration of the available services.", @@ -123,10 +126,6 @@ kubectl plugin mobile get clientconfig`, ret = append(ret, svcConfig) } } - servicePinningHashes, err := retrieveHTTPSConfigForServices(ret) - if err != nil { - return errors.Wrap(err, "unable to append https configuration for services.") - } outputJSON := ServiceConfigs{ Version: 1, @@ -134,8 +133,17 @@ kubectl plugin mobile get clientconfig`, Namespace: ns, ClientID: clientID, ClusterName: ccc.clusterHost, - Https: servicePinningHashes, } + + // If the flag is set then include another key named 'https' which contains certificate hashes. + if includeCertificateHashes { + servicePinningHashes, err := retrieveHTTPSConfigForServices(outputJSON.Services, allowInvalidCertificateHashes) + if err != nil { + return errors.Wrap(err, "Could not append HTTPS configuration for services") + } + outputJSON.Https = servicePinningHashes + } + if err := ccc.Out.Render("get"+cmd.Name(), outputType(cmd.Flags()), outputJSON); err != nil { return errors.Wrap(err, fmt.Sprintf(output.FailedToOutPutInFormat, "ServiceConfig", outputType(cmd.Flags()))) } @@ -159,5 +167,8 @@ kubectl plugin mobile get clientconfig`, table.Render() return nil }) + + cmd.Flags().BoolVar(&allowInvalidCertificateHashes, "allow-invalid-certs", false, "include certificate hashes for services with invalid/self-signed certificates") + cmd.Flags().BoolVar(&includeCertificateHashes, "include-cert-hashes", false, "include certificate hashes for services in the client config") return cmd } diff --git a/pkg/cmd/clientConfig_test.go b/pkg/cmd/clientConfig_test.go index 1d5fd4e..e2b7f29 100644 --- a/pkg/cmd/clientConfig_test.go +++ b/pkg/cmd/clientConfig_test.go @@ -115,8 +115,7 @@ func TestClientConfigCmd_GetClientConfigCmd(t *testing.T) { "clusterName": "test", "namespace": "testing-ns", "clientId": "client-id", - "services": [], - "https": [] + "services": [] }` if strings.TrimSpace(out.String()) != expected { return errors.New(fmt.Sprintf("expected: '%v', got: '%v'", expected, strings.TrimSpace(out.String()))) @@ -213,8 +212,7 @@ func TestClientConfigCmd_GetClientConfigCmd(t *testing.T) { "url": "", "config": {} } - ], - "https": [] + ] }` if strings.TrimSpace(out.String()) != expected { return errors.New(fmt.Sprintf("expected: '%v', got: '%v'", expected, strings.TrimSpace(out.String()))) @@ -325,8 +323,7 @@ func TestClientConfigCmd_GetClientConfigCmd(t *testing.T) { "url": "", "config": {} } - ], - "https": [] + ] }` if strings.TrimSpace(out.String()) != expected { return errors.New(fmt.Sprintf("expected: '%v', got: '%v'", expected, strings.TrimSpace(out.String()))) diff --git a/pkg/cmd/convert.go b/pkg/cmd/convert.go index a58db81..20da091 100644 --- a/pkg/cmd/convert.go +++ b/pkg/cmd/convert.go @@ -24,17 +24,16 @@ import ( "fmt" "github.com/pkg/errors" "net/url" - "os" ) func isClientConfigKey(key string) bool { return key == "url" || key == "name" || key == "type" || key == "id" } -func retrieveHTTPSConfigForServices(services []*ServiceConfig) ([]*CertificatePinningHash, error) { - httpsConfig := []*CertificatePinningHash{} - for _, svc := range services { - pinningHash, err := retrieveHTTPSConfigForService(svc) +func retrieveHTTPSConfigForServices(svcConfigs []*ServiceConfig, includeInvalidCerts bool) ([]*CertificatePinningHash, error) { + httpsConfig := make([]*CertificatePinningHash, 0) + for _, svc := range svcConfigs { + pinningHash, err := retrieveHTTPSConfigForService(svc, includeInvalidCerts) if err != nil { return nil, err } @@ -45,33 +44,32 @@ func retrieveHTTPSConfigForServices(services []*ServiceConfig) ([]*CertificatePi return httpsConfig, nil } -func retrieveHTTPSConfigForService(service *ServiceConfig) (*CertificatePinningHash, error) { - serviceURL, err := url.Parse(service.URL) +func retrieveHTTPSConfigForService(svcConfig *ServiceConfig, allowInvalidCert bool) (*CertificatePinningHash, error) { + // Parse the services URL, if it's not HTTPS then don't attempt to retrieve a cert for it. + serviceURL, err := url.Parse(svcConfig.URL) if err != nil { - return nil, err + return nil, errors.Wrap(err, "Could not parse service URL "+svcConfig.URL) } if serviceURL.Scheme != "https" { return nil, nil } - allowInvalidCertificates := os.Getenv("AEROGEAR_ALLOW_INVALID_CERTS") == "true" - - certificate, err := retrieveCertificateForURL(serviceURL, allowInvalidCertificates) + certificate, err := retrieveCertificateForURL(serviceURL, allowInvalidCert) if err != nil { - return nil, err + return nil, errors.Wrap(err, "Could not retrieve certificate for service URL "+serviceURL.String()) } hasher := sha256.New() _, err = hasher.Write(certificate.RawSubjectPublicKeyInfo) if err != nil { - return nil, err + return nil, errors.Wrap(err, "Could not write public key to buffer for hashing") } pinningHash := base64.StdEncoding.EncodeToString(hasher.Sum(nil)) - return &CertificatePinningHash{serviceURL.String(), pinningHash}, nil + return &CertificatePinningHash{serviceURL.Host, pinningHash}, nil } -func retrieveCertificateForURL(url *url.URL, allowInvalidCertificates bool) (*x509.Certificate, error) { - // If the 443 port is not appended to the URLs host then we should append it. +func retrieveCertificateForURL(url *url.URL, allowInvalidCert bool) (*x509.Certificate, error) { + // If the 443 port is not appended to the URLs host then we should append it or tls.Dial will fail. port := "443" if url.Port() != "" { port = url.Port() @@ -79,7 +77,7 @@ func retrieveCertificateForURL(url *url.URL, allowInvalidCertificates bool) (*x5 hostURL := fmt.Sprintf("%s:%s", url.Host, port) conn, err := tls.Dial("tcp", hostURL, &tls.Config{ - InsecureSkipVerify: allowInvalidCertificates, + InsecureSkipVerify: allowInvalidCert, }) if err != nil { diff --git a/pkg/cmd/types.go b/pkg/cmd/types.go index 988dec8..9ed9cab 100644 --- a/pkg/cmd/types.go +++ b/pkg/cmd/types.go @@ -76,7 +76,7 @@ type ServiceConfigs struct { Namespace string `json:"namespace"` ClientID string `json:"clientId,omitempty"` Services []*ServiceConfig `json:"services"` - Https []*CertificatePinningHash `json:"https"` + Https []*CertificatePinningHash `json:"https,omitempty"` } //ServiceConfig is the configuration for a specific service From 9a4dd12e6664d403881ae9dafd2b8698be499328 Mon Sep 17 00:00:00 2001 From: Aiden Keating Date: Wed, 28 Mar 2018 00:10:30 +0100 Subject: [PATCH 5/9] Add certificatePinning key to https --- pkg/cmd/clientConfig.go | 2 +- pkg/cmd/convert.go | 4 ++-- pkg/cmd/types.go | 16 ++++++++++------ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pkg/cmd/clientConfig.go b/pkg/cmd/clientConfig.go index c2a61fa..bf3edcc 100644 --- a/pkg/cmd/clientConfig.go +++ b/pkg/cmd/clientConfig.go @@ -141,7 +141,7 @@ kubectl plugin mobile get clientconfig`, if err != nil { return errors.Wrap(err, "Could not append HTTPS configuration for services") } - outputJSON.Https = servicePinningHashes + outputJSON.Https.CertificatePinning = servicePinningHashes } if err := ccc.Out.Render("get"+cmd.Name(), outputType(cmd.Flags()), outputJSON); err != nil { diff --git a/pkg/cmd/convert.go b/pkg/cmd/convert.go index 20da091..46d217c 100644 --- a/pkg/cmd/convert.go +++ b/pkg/cmd/convert.go @@ -15,15 +15,15 @@ package cmd import ( - "strings" - "k8s.io/client-go/pkg/api/v1" "crypto/sha256" "crypto/tls" "crypto/x509" "encoding/base64" "fmt" "github.com/pkg/errors" + "k8s.io/client-go/pkg/api/v1" "net/url" + "strings" ) func isClientConfigKey(key string) bool { diff --git a/pkg/cmd/types.go b/pkg/cmd/types.go index 9ed9cab..1ad04fc 100644 --- a/pkg/cmd/types.go +++ b/pkg/cmd/types.go @@ -71,12 +71,12 @@ type SecretConvertor interface { //ServiceConfigs are collection of configurations for services in a specific namespace type ServiceConfigs struct { - Version int `json:"version"` - ClusterName string `json:"clusterName"` - Namespace string `json:"namespace"` - ClientID string `json:"clientId,omitempty"` - Services []*ServiceConfig `json:"services"` - Https []*CertificatePinningHash `json:"https,omitempty"` + Version int `json:"version"` + ClusterName string `json:"clusterName"` + Namespace string `json:"namespace"` + ClientID string `json:"clientId,omitempty"` + Services []*ServiceConfig `json:"services"` + Https HttpsConfig `json:"https,omitempty"` } //ServiceConfig is the configuration for a specific service @@ -88,6 +88,10 @@ type ServiceConfig struct { Config map[string]interface{} `json:"config"` } +type HttpsConfig struct { + CertificatePinning []*CertificatePinningHash `json:"certificatePinning,omitempty"` +} + type CertificatePinningHash struct { Host string `json:"host"` CertificateHash string `json:"certificateHash"` From c65a5dc008e3e0e864aabf804124d95722a97d74 Mon Sep 17 00:00:00 2001 From: Aiden Keating Date: Wed, 28 Mar 2018 12:49:49 +0100 Subject: [PATCH 6/9] Update https format --- pkg/cmd/clientConfig.go | 16 +++++++++------- pkg/cmd/types.go | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/pkg/cmd/clientConfig.go b/pkg/cmd/clientConfig.go index bf3edcc..d7d4407 100644 --- a/pkg/cmd/clientConfig.go +++ b/pkg/cmd/clientConfig.go @@ -53,8 +53,8 @@ func NewClientConfigCmd(k8Client kubernetes.Interface, mobileClient mobile.Inter // GetClientConfigCmd returns a cobra command object for getting client configs func (ccc *ClientConfigCmd) GetClientConfigCmd() *cobra.Command { - var includeCertificateHashes bool - var allowInvalidCertificateHashes bool + var includeCertificatePins bool + var allowSelfSignedCerts bool cmd := &cobra.Command{ Use: "clientconfig ", @@ -136,12 +136,14 @@ kubectl plugin mobile get clientconfig`, } // If the flag is set then include another key named 'https' which contains certificate hashes. - if includeCertificateHashes { - servicePinningHashes, err := retrieveHTTPSConfigForServices(outputJSON.Services, allowInvalidCertificateHashes) + if includeCertificatePins { + servicePinningHashes, err := retrieveHTTPSConfigForServices(outputJSON.Services, allowSelfSignedCerts) if err != nil { return errors.Wrap(err, "Could not append HTTPS configuration for services") } - outputJSON.Https.CertificatePinning = servicePinningHashes + outputJSON.Https = &HttpsConfig{ + CertificatePinning: servicePinningHashes, + } } if err := ccc.Out.Render("get"+cmd.Name(), outputType(cmd.Flags()), outputJSON); err != nil { @@ -168,7 +170,7 @@ kubectl plugin mobile get clientconfig`, return nil }) - cmd.Flags().BoolVar(&allowInvalidCertificateHashes, "allow-invalid-certs", false, "include certificate hashes for services with invalid/self-signed certificates") - cmd.Flags().BoolVar(&includeCertificateHashes, "include-cert-hashes", false, "include certificate hashes for services in the client config") + cmd.Flags().BoolVar(&allowSelfSignedCerts, "allow-self-signed-certs", false, "include certificate hashes for services with invalid/self-signed certificates") + cmd.Flags().BoolVar(&includeCertificatePins, "include-cert-pins", false, "include certificate hashes for services in the client config") return cmd } diff --git a/pkg/cmd/types.go b/pkg/cmd/types.go index 1ad04fc..c5d76e5 100644 --- a/pkg/cmd/types.go +++ b/pkg/cmd/types.go @@ -76,7 +76,7 @@ type ServiceConfigs struct { Namespace string `json:"namespace"` ClientID string `json:"clientId,omitempty"` Services []*ServiceConfig `json:"services"` - Https HttpsConfig `json:"https,omitempty"` + Https *HttpsConfig `json:"https,omitempty"` } //ServiceConfig is the configuration for a specific service @@ -89,7 +89,7 @@ type ServiceConfig struct { } type HttpsConfig struct { - CertificatePinning []*CertificatePinningHash `json:"certificatePinning,omitempty"` + CertificatePinning []*CertificatePinningHash `json:"certificatePins,omitempty"` } type CertificatePinningHash struct { From 6cc20a118e4abb6297cd75e54db13d83ff18aa3e Mon Sep 17 00:00:00 2001 From: Aiden Keating Date: Wed, 14 Mar 2018 10:54:39 +0000 Subject: [PATCH 7/9] Update tests --- integration/getClientConfigTestData/no-client-id.golden | 6 ++++-- pkg/cmd/types.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/integration/getClientConfigTestData/no-client-id.golden b/integration/getClientConfigTestData/no-client-id.golden index 7a5b72c..314eb5c 100644 --- a/integration/getClientConfigTestData/no-client-id.golden +++ b/integration/getClientConfigTestData/no-client-id.golden @@ -2,9 +2,11 @@ Usage: mobile get clientconfig [flags] Flags: - -h, --help help for clientconfig + --allow-self-signed-certs include certificate hashes for services with invalid/self-signed certificates + -h, --help help for clientconfig + --include-cert-pins include certificate hashes for services in the client config Global Flags: --namespace string --namespace=myproject -o, --output string -o=json -o=template (default "table") - -q, --quiet -q all non essential output will be stopped + -q, --quiet -q all non essential output will be stopped \ No newline at end of file diff --git a/pkg/cmd/types.go b/pkg/cmd/types.go index c5d76e5..d8c17b8 100644 --- a/pkg/cmd/types.go +++ b/pkg/cmd/types.go @@ -76,7 +76,7 @@ type ServiceConfigs struct { Namespace string `json:"namespace"` ClientID string `json:"clientId,omitempty"` Services []*ServiceConfig `json:"services"` - Https *HttpsConfig `json:"https,omitempty"` + Https *HttpsConfig `json:"https,omitempty"` } //ServiceConfig is the configuration for a specific service From b9bd1a9330b2b6d143acef454493601dc6176fd0 Mon Sep 17 00:00:00 2001 From: Aiden Keating Date: Wed, 28 Mar 2018 14:23:52 +0100 Subject: [PATCH 8/9] pick 02e0349 TEMP Update integration test outputs --- .../no-client-id.golden | 8 +-- .../getServicesTestData/json-output.golden | 52 +++++++++---------- .../getServicesTestData/no-args.golden | 5 +- .../getServicesTestData/table-output.golden | 5 +- 4 files changed, 32 insertions(+), 38 deletions(-) diff --git a/integration/getClientConfigTestData/no-client-id.golden b/integration/getClientConfigTestData/no-client-id.golden index 314eb5c..6b10705 100644 --- a/integration/getClientConfigTestData/no-client-id.golden +++ b/integration/getClientConfigTestData/no-client-id.golden @@ -2,11 +2,11 @@ Usage: mobile get clientconfig [flags] Flags: - --allow-self-signed-certs include certificate hashes for services with invalid/self-signed certificates - -h, --help help for clientconfig - --include-cert-pins include certificate hashes for services in the client config + -h, --help help for clientconfig + --include-cert-pins include certificate hashes for services in the client config + --insecure-skip-tls-verify include certificate hashes for services with invalid/self-signed certificates Global Flags: --namespace string --namespace=myproject -o, --output string -o=json -o=template (default "table") - -q, --quiet -q all non essential output will be stopped \ No newline at end of file + -q, --quiet -q all non essential output will be stopped diff --git a/integration/getServicesTestData/json-output.golden b/integration/getServicesTestData/json-output.golden index 8f63ff1..0ea99c6 100644 --- a/integration/getServicesTestData/json-output.golden +++ b/integration/getServicesTestData/json-output.golden @@ -5,21 +5,21 @@ "metadata": { "name": "1522a4d0e2fbf86a26cbe096eb1b6b2d", "selfLink": "/apis/servicecatalog.k8s.io/v1beta1/clusterserviceclasses/1522a4d0e2fbf86a26cbe096eb1b6b2d", - "uid": "5c206f71-2c6e-11e8-b7c1-0a580a820054", - "resourceVersion": "37621601", - "creationTimestamp": "2018-03-20T18:41:53Z" + "uid": "70e74a1b-37e8-11e8-8387-0242ac110003", + "resourceVersion": "55", + "creationTimestamp": "2018-04-04T09:13:30Z" }, "spec": { "clusterServiceBrokerName": "ansible-service-broker", "externalName": "dh-unifiedpush-apb", "externalID": "1522a4d0e2fbf86a26cbe096eb1b6b2d", "description": "AeroGear UnifiedPush Server", - "bindable": false, + "bindable": true, "binding_retrievable": false, "planUpdatable": false, "externalMetadata": { "dependencies": [ - "MySQL:55" + "POSTGRES:95" ], "displayName": "AeroGear UPS", "documentationUrl": "https://aerogear.org/push", @@ -39,9 +39,9 @@ "metadata": { "name": "2b825339e8d685a78476621a252beea8", "selfLink": "/apis/servicecatalog.k8s.io/v1beta1/clusterserviceclasses/2b825339e8d685a78476621a252beea8", - "uid": "5c3c2124-2c6e-11e8-b7c1-0a580a820054", - "resourceVersion": "37621606", - "creationTimestamp": "2018-03-20T18:41:54Z" + "uid": "70c8d30c-37e8-11e8-8387-0242ac110003", + "resourceVersion": "37", + "creationTimestamp": "2018-04-04T09:13:29Z" }, "spec": { "clusterServiceBrokerName": "ansible-service-broker", @@ -70,9 +70,9 @@ "metadata": { "name": "9623d53183cc78619f888ea8499c678e", "selfLink": "/apis/servicecatalog.k8s.io/v1beta1/clusterserviceclasses/9623d53183cc78619f888ea8499c678e", - "uid": "20abc8b7-1339-11e8-a1f5-0a580a820006", - "resourceVersion": "25057309", - "creationTimestamp": "2018-02-16T16:47:51Z" + "uid": "709e246a-37e8-11e8-8387-0242ac110003", + "resourceVersion": "33", + "creationTimestamp": "2018-04-04T09:13:29Z" }, "spec": { "clusterServiceBrokerName": "ansible-service-broker", @@ -94,16 +94,16 @@ ] }, "status": { - "removedFromBrokerCatalog": true + "removedFromBrokerCatalog": false } }, { "metadata": { "name": "a0c0c2478554458d5c77abc95f0473a3", "selfLink": "/apis/servicecatalog.k8s.io/v1beta1/clusterserviceclasses/a0c0c2478554458d5c77abc95f0473a3", - "uid": "5c256f33-2c6e-11e8-b7c1-0a580a820054", - "resourceVersion": "37621605", - "creationTimestamp": "2018-03-20T18:41:53Z" + "uid": "709f0ccd-37e8-11e8-8387-0242ac110003", + "resourceVersion": "35", + "creationTimestamp": "2018-04-04T09:13:29Z" }, "spec": { "clusterServiceBrokerName": "ansible-service-broker", @@ -133,9 +133,9 @@ "metadata": { "name": "b95513950bb3f132de25d58fb75f8dca", "selfLink": "/apis/servicecatalog.k8s.io/v1beta1/clusterserviceclasses/b95513950bb3f132de25d58fb75f8dca", - "uid": "20ac53be-1339-11e8-a1f5-0a580a820006", - "resourceVersion": "37164074", - "creationTimestamp": "2018-02-16T16:47:51Z" + "uid": "709da246-37e8-11e8-8387-0242ac110003", + "resourceVersion": "32", + "creationTimestamp": "2018-04-04T09:13:29Z" }, "spec": { "clusterServiceBrokerName": "ansible-service-broker", @@ -160,16 +160,16 @@ ] }, "status": { - "removedFromBrokerCatalog": true + "removedFromBrokerCatalog": false } }, { "metadata": { "name": "c57e94c36c1e7f6bb41cf7c589d9eb08", "selfLink": "/apis/servicecatalog.k8s.io/v1beta1/clusterserviceclasses/c57e94c36c1e7f6bb41cf7c589d9eb08", - "uid": "20add41c-1339-11e8-a1f5-0a580a820006", - "resourceVersion": "16418101", - "creationTimestamp": "2018-02-16T16:47:51Z" + "uid": "70aa41b6-37e8-11e8-8387-0242ac110003", + "resourceVersion": "36", + "creationTimestamp": "2018-04-04T09:13:29Z" }, "spec": { "clusterServiceBrokerName": "ansible-service-broker", @@ -192,16 +192,16 @@ ] }, "status": { - "removedFromBrokerCatalog": true + "removedFromBrokerCatalog": false } }, { "metadata": { "name": "f69b4a4a744c3848d352b7321a8457d1", "selfLink": "/apis/servicecatalog.k8s.io/v1beta1/clusterserviceclasses/f69b4a4a744c3848d352b7321a8457d1", - "uid": "5c23135c-2c6e-11e8-b7c1-0a580a820054", - "resourceVersion": "37621602", - "creationTimestamp": "2018-03-20T18:41:53Z" + "uid": "709c9f70-37e8-11e8-8387-0242ac110003", + "resourceVersion": "30", + "creationTimestamp": "2018-04-04T09:13:29Z" }, "spec": { "clusterServiceBrokerName": "ansible-service-broker", diff --git a/integration/getServicesTestData/no-args.golden b/integration/getServicesTestData/no-args.golden index 6199230..046c4ad 100644 --- a/integration/getServicesTestData/no-args.golden +++ b/integration/getServicesTestData/no-args.golden @@ -1,10 +1,7 @@ +--------------------------+------------------+--------------------------------+ | NAME | INTEGRATIONS | PARAMETERS | +--------------------------+------------------+--------------------------------+ -| ups | | MYSQL_DATABASE, | -| | | MYSQL_USER, MYSQL_VERSION, | -| | | _MYSQL_PASSWORD, | -| | | _MYSQL_ROOT_PASSWORD | +| ups | | | | 3scale | | THREESCALE_ACCESS_TOKEN, | | | | THREESCALE_DOMAIN, | | | | THREESCALE_ENABLE_CORS, | diff --git a/integration/getServicesTestData/table-output.golden b/integration/getServicesTestData/table-output.golden index 6199230..046c4ad 100644 --- a/integration/getServicesTestData/table-output.golden +++ b/integration/getServicesTestData/table-output.golden @@ -1,10 +1,7 @@ +--------------------------+------------------+--------------------------------+ | NAME | INTEGRATIONS | PARAMETERS | +--------------------------+------------------+--------------------------------+ -| ups | | MYSQL_DATABASE, | -| | | MYSQL_USER, MYSQL_VERSION, | -| | | _MYSQL_PASSWORD, | -| | | _MYSQL_ROOT_PASSWORD | +| ups | | | | 3scale | | THREESCALE_ACCESS_TOKEN, | | | | THREESCALE_DOMAIN, | | | | THREESCALE_ENABLE_CORS, | From dd14003e164f48f27d501a5cca5be61bb987efdb Mon Sep 17 00:00:00 2001 From: Aiden Keating Date: Wed, 4 Apr 2018 15:51:54 +0100 Subject: [PATCH 9/9] Update self signed certs flag inline with oc --- pkg/cmd/clientConfig.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/clientConfig.go b/pkg/cmd/clientConfig.go index d7d4407..22621dc 100644 --- a/pkg/cmd/clientConfig.go +++ b/pkg/cmd/clientConfig.go @@ -54,7 +54,7 @@ func NewClientConfigCmd(k8Client kubernetes.Interface, mobileClient mobile.Inter // GetClientConfigCmd returns a cobra command object for getting client configs func (ccc *ClientConfigCmd) GetClientConfigCmd() *cobra.Command { var includeCertificatePins bool - var allowSelfSignedCerts bool + var skipTLSVerification bool cmd := &cobra.Command{ Use: "clientconfig ", @@ -137,7 +137,7 @@ kubectl plugin mobile get clientconfig`, // If the flag is set then include another key named 'https' which contains certificate hashes. if includeCertificatePins { - servicePinningHashes, err := retrieveHTTPSConfigForServices(outputJSON.Services, allowSelfSignedCerts) + servicePinningHashes, err := retrieveHTTPSConfigForServices(outputJSON.Services, skipTLSVerification) if err != nil { return errors.Wrap(err, "Could not append HTTPS configuration for services") } @@ -170,7 +170,7 @@ kubectl plugin mobile get clientconfig`, return nil }) - cmd.Flags().BoolVar(&allowSelfSignedCerts, "allow-self-signed-certs", false, "include certificate hashes for services with invalid/self-signed certificates") + cmd.Flags().BoolVar(&skipTLSVerification, "insecure-skip-tls-verify", false, "include certificate hashes for services with invalid/self-signed certificates") cmd.Flags().BoolVar(&includeCertificatePins, "include-cert-pins", false, "include certificate hashes for services in the client config") return cmd }