Skip to content

Commit

Permalink
Merge pull request #71 from aidenkeating/certificate-pinning-hashes
Browse files Browse the repository at this point in the history
Include certificate pinning hashes in the service configuration
  • Loading branch information
maleck13 authored Apr 4, 2018
2 parents 5eec520 + dd14003 commit b9e55a9
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 41 deletions.
29 changes: 26 additions & 3 deletions glide.lock

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

4 changes: 3 additions & 1 deletion glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@ import:
- package: github.com/olekukonko/tablewriter
version: 65fec0d89a572b4367094e2058d3ebe667de3b60
- package: github.com/mattn/go-runewidth
version: ~0.0.2
version: ~0.0.2
- package: github.com/goreleaser/goreleaser
version: v0.58.0
4 changes: 3 additions & 1 deletion integration/getClientConfigTestData/no-client-id.golden
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ Usage:
mobile get clientconfig <clientID> [flags]

Flags:
-h, --help help for clientconfig
-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
Expand Down
52 changes: 26 additions & 26 deletions integration/getServicesTestData/json-output.golden
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
5 changes: 1 addition & 4 deletions integration/getServicesTestData/no-args.golden
Original file line number Diff line number Diff line change
@@ -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, |
Expand Down
5 changes: 1 addition & 4 deletions integration/getServicesTestData/table-output.golden
Original file line number Diff line number Diff line change
@@ -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, |
Expand Down
18 changes: 18 additions & 0 deletions pkg/cmd/clientConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 includeCertificatePins bool
var skipTLSVerification bool

cmd := &cobra.Command{
Use: "clientconfig <clientID>",
Short: "get clientconfig returns a client ready filtered configuration of the available services.",
Expand Down Expand Up @@ -131,6 +134,18 @@ kubectl plugin mobile get clientconfig`,
ClientID: clientID,
ClusterName: ccc.clusterHost,
}

// If the flag is set then include another key named 'https' which contains certificate hashes.
if includeCertificatePins {
servicePinningHashes, err := retrieveHTTPSConfigForServices(outputJSON.Services, skipTLSVerification)
if err != nil {
return errors.Wrap(err, "Could not append HTTPS configuration for services")
}
outputJSON.Https = &HttpsConfig{
CertificatePinning: 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())))
}
Expand All @@ -154,5 +169,8 @@ kubectl plugin mobile get clientconfig`,
table.Render()
return nil
})

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
}
67 changes: 65 additions & 2 deletions pkg/cmd/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,77 @@
package cmd

import (
"strings"

"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 {
return key == "url" || key == "name" || key == "type" || key == "id"
}

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
}
if pinningHash != nil {
httpsConfig = append(httpsConfig, pinningHash)
}
}
return httpsConfig, nil
}

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, errors.Wrap(err, "Could not parse service URL "+svcConfig.URL)
}
if serviceURL.Scheme != "https" {
return nil, nil
}

certificate, err := retrieveCertificateForURL(serviceURL, allowInvalidCert)
if err != nil {
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, errors.Wrap(err, "Could not write public key to buffer for hashing")
}
pinningHash := base64.StdEncoding.EncodeToString(hasher.Sum(nil))
return &CertificatePinningHash{serviceURL.Host, pinningHash}, nil
}

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()
}
hostURL := fmt.Sprintf("%s:%s", url.Host, port)

conn, err := tls.Dial("tcp", hostURL, &tls.Config{
InsecureSkipVerify: allowInvalidCert,
})

if err != nil {
return nil, errors.Wrap(err, "Could not retrieve certificate for URL "+url.String())
}
return conn.ConnectionState().PeerCertificates[0], nil
}

func convertSecretToMobileService(s v1.Secret) *Service {
params := map[string]string{}
for key, value := range s.Data {
Expand All @@ -32,6 +94,7 @@ func convertSecretToMobileService(s v1.Secret) *Service {
}
}
external := s.Labels["external"] == "true"

return &Service{
Namespace: s.Labels["namespace"],
ID: s.Name,
Expand Down
10 changes: 10 additions & 0 deletions pkg/cmd/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type ServiceConfigs struct {
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
Expand All @@ -87,6 +88,15 @@ type ServiceConfig struct {
Config map[string]interface{} `json:"config"`
}

type HttpsConfig struct {
CertificatePinning []*CertificatePinningHash `json:"certificatePins,omitempty"`
}

type CertificatePinningHash struct {
Host string `json:"host"`
CertificateHash string `json:"certificateHash"`
}

// defaultSecretConvertor will provide a default secret to config conversion
type defaultSecretConvertor struct{}

Expand Down

0 comments on commit b9e55a9

Please sign in to comment.