From 080cb9cf6e3af579ec9c8e0c3baad30b6df2a82a Mon Sep 17 00:00:00 2001 From: Ripta Pasay Date: Mon, 14 May 2018 23:44:29 -0700 Subject: [PATCH 1/3] Add cluster CA certificate into kubeconfig if one exists --- kuberos.go | 24 +++++++++++++++++++++++- kuberos_test.go | 1 + 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/kuberos.go b/kuberos.go index fc72bd7..e847adf 100644 --- a/kuberos.go +++ b/kuberos.go @@ -4,8 +4,11 @@ import ( "crypto/sha256" "encoding/json" "fmt" + "io/ioutil" "net/http" "net/url" + "os" + "path/filepath" "github.com/negz/kuberos/extractor" @@ -14,6 +17,7 @@ import ( "github.com/pkg/errors" "go.uber.org/zap" "golang.org/x/oauth2" + "k8s.io/api/core/v1" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" ) @@ -23,6 +27,9 @@ const ( // be redirected after authentication. DefaultKubeCfgEndpoint = "ui" + // DefaultAPITokenMountPath is the default mount path for API tokens + DefaultAPITokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount" + schemeHTTP = "http" schemeHTTPS = "https" @@ -358,9 +365,24 @@ func populateUser(cfg *api.Config, p *extractor.OIDCAuthenticationParams) api.Co }, }, } + for name, cluster := range cfg.Clusters { + // If the cluster definition does not come with certificate-authority-data nor + // certificate-authority, then check if kuberos has access to the cluster's CA + // certificate and include it when possible. Assume all errors are non-fatal. + if len(cluster.CertificateAuthorityData) == 0 && cluster.CertificateAuthority == "" { + caPath := filepath.Join(DefaultAPITokenMountPath, v1.ServiceAccountRootCAKey) + if caFile, err := os.Open(caPath); err == nil { + if caCert, err := ioutil.ReadAll(caFile); err == nil { + cluster.CertificateAuthorityData = caCert + } + } + } c.Clusters[name] = cluster - c.Contexts[name] = &api.Context{Cluster: name, AuthInfo: p.Username} + c.Contexts[name] = &api.Context{ + Cluster: name, + AuthInfo: p.Username, + } } return c } diff --git a/kuberos_test.go b/kuberos_test.go index 37cfacc..0c5e11b 100644 --- a/kuberos_test.go +++ b/kuberos_test.go @@ -125,6 +125,7 @@ func TestPopulateUser(t *testing.T) { }, }, }, + CurrentContext: "a", }, }, { From f2ff3011c7fd2c9758011aa3cd1ae3b00ced5a11 Mon Sep 17 00:00:00 2001 From: Ripta Pasay Date: Wed, 16 May 2018 01:00:55 -0700 Subject: [PATCH 2/3] Add `populateUser` test cases for auto-populated CA certificate --- kuberos.go | 8 +++-- kuberos_test.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/kuberos.go b/kuberos.go index e847adf..9fb95a9 100644 --- a/kuberos.go +++ b/kuberos.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "net/http" "net/url" - "os" "path/filepath" "github.com/negz/kuberos/extractor" @@ -15,6 +14,7 @@ import ( oidc "github.com/coreos/go-oidc" "github.com/gorilla/schema" "github.com/pkg/errors" + "github.com/spf13/afero" "go.uber.org/zap" "golang.org/x/oauth2" "k8s.io/api/core/v1" @@ -74,6 +74,8 @@ var ( decoder = schema.NewDecoder() + appFs = afero.NewOsFs() + approvalConsent = oauth2.SetAuthURLParam("prompt", "consent") ) @@ -372,10 +374,12 @@ func populateUser(cfg *api.Config, p *extractor.OIDCAuthenticationParams) api.Co // certificate and include it when possible. Assume all errors are non-fatal. if len(cluster.CertificateAuthorityData) == 0 && cluster.CertificateAuthority == "" { caPath := filepath.Join(DefaultAPITokenMountPath, v1.ServiceAccountRootCAKey) - if caFile, err := os.Open(caPath); err == nil { + if caFile, err := appFs.Open(caPath); err == nil { if caCert, err := ioutil.ReadAll(caFile); err == nil { cluster.CertificateAuthorityData = caCert } + } else { + fmt.Printf("Error: %+v\n", err) } } c.Clusters[name] = cluster diff --git a/kuberos_test.go b/kuberos_test.go index 0c5e11b..23a9693 100644 --- a/kuberos_test.go +++ b/kuberos_test.go @@ -8,6 +8,7 @@ import ( oidc "github.com/coreos/go-oidc" "github.com/go-test/deep" + "github.com/spf13/afero" "golang.org/x/oauth2" "github.com/negz/kuberos/extractor" @@ -83,6 +84,7 @@ func TestPopulateUser(t *testing.T) { cases := []struct { name string cfg *api.Config + files map[string]string params *extractor.OIDCAuthenticationParams want api.Config }{ @@ -94,6 +96,7 @@ func TestPopulateUser(t *testing.T) { "b": &api.Cluster{Server: "https://example.net", CertificateAuthorityData: []byte("PAM")}, }, }, + files: map[string]string{}, params: &extractor.OIDCAuthenticationParams{ Username: "example@example.org", ClientID: "id", @@ -125,7 +128,6 @@ func TestPopulateUser(t *testing.T) { }, }, }, - CurrentContext: "a", }, }, { @@ -137,6 +139,7 @@ func TestPopulateUser(t *testing.T) { }, CurrentContext: "a", }, + files: map[string]string{}, params: &extractor.OIDCAuthenticationParams{ Username: "example@example.org", ClientID: "id", @@ -171,10 +174,97 @@ func TestPopulateUser(t *testing.T) { CurrentContext: "a", }, }, + { + name: "SingleClusterWithCAOnDisk", + cfg: &api.Config{ + Clusters: map[string]*api.Cluster{ + "a": &api.Cluster{Server: "https://example.org"}, + }, + }, + files: map[string]string{ + "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt": "PEM", + }, + params: &extractor.OIDCAuthenticationParams{ + Username: "example@example.org", + ClientID: "id", + ClientSecret: "secret", + IDToken: "token", + RefreshToken: "refresh", + IssuerURL: "https://example.org", + }, + want: api.Config{ + Clusters: map[string]*api.Cluster{ + "a": &api.Cluster{Server: "https://example.org", CertificateAuthorityData: []byte("PEM")}, + }, + Contexts: map[string]*api.Context{ + "a": &api.Context{AuthInfo: "example@example.org", Cluster: "a"}, + }, + AuthInfos: map[string]*api.AuthInfo{ + "example@example.org": &api.AuthInfo{ + AuthProvider: &api.AuthProviderConfig{ + Name: templateAuthProvider, + Config: map[string]string{ + templateOIDCClientID: "id", + templateOIDCClientSecret: "secret", + templateOIDCIDToken: "token", + templateOIDCRefreshToken: "refresh", + templateOIDCIssuer: "https://example.org", + }, + }, + }, + }, + }, + }, + { + name: "SingleClusterWithoutCA", + cfg: &api.Config{ + Clusters: map[string]*api.Cluster{ + "a": &api.Cluster{Server: "https://example.org"}, + }, + }, + files: map[string]string{}, + params: &extractor.OIDCAuthenticationParams{ + Username: "example@example.org", + ClientID: "id", + ClientSecret: "secret", + IDToken: "token", + RefreshToken: "refresh", + IssuerURL: "https://example.org", + }, + want: api.Config{ + Clusters: map[string]*api.Cluster{ + "a": &api.Cluster{Server: "https://example.org"}, + }, + Contexts: map[string]*api.Context{ + "a": &api.Context{AuthInfo: "example@example.org", Cluster: "a"}, + }, + AuthInfos: map[string]*api.AuthInfo{ + "example@example.org": &api.AuthInfo{ + AuthProvider: &api.AuthProviderConfig{ + Name: templateAuthProvider, + Config: map[string]string{ + templateOIDCClientID: "id", + templateOIDCClientSecret: "secret", + templateOIDCIDToken: "token", + templateOIDCRefreshToken: "refresh", + templateOIDCIssuer: "https://example.org", + }, + }, + }, + }, + }, + }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { + appFs = afero.NewMemMapFs() + for filename, content := range tt.files { + if err := afero.WriteFile(appFs, filename, []byte(content), 0644); err != nil { + t.Errorf("error writing file %q: %v", filename, err) + } + } + got := populateUser(tt.cfg, tt.params) if diff := deep.Equal(got, tt.want); diff != nil { t.Errorf("populateUser(...): got != want: %v", diff) From 7e2f3c30ccb69517bd372dfabc1efd79c2acac61 Mon Sep 17 00:00:00 2001 From: Ripta Pasay Date: Wed, 16 May 2018 01:08:37 -0700 Subject: [PATCH 3/3] Add afero to glide --- glide.lock | 8 ++++++-- glide.yaml | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/glide.lock b/glide.lock index 59b895d..9348887 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 586f06dac64626a980055d13a01b7e19966239a07ac7a1bdffd61c52b1e33219 -updated: 2018-01-16T15:48:55.20164-08:00 +hash: 76fa71a7a967d86b98a17d084d6ff4a1a572f96418730d4fad33c82ff5d282cc +updated: 2018-05-16T01:07:31.651399512-07:00 imports: - name: github.com/alecthomas/template version: a0175ee3bccc567396460bf5acd36800cb10c49c @@ -80,6 +80,10 @@ imports: version: fd36b3595eb2ec8da4b8153b107f7ea08504899d subpackages: - fs +- name: github.com/spf13/afero + version: 63644898a8da0bc22138abf860edaf5277b6102e + subpackages: + - mem - name: github.com/spf13/pflag version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7 - name: go.uber.org/atomic diff --git a/glide.yaml b/glide.yaml index ebc4a84..cb4122e 100644 --- a/glide.yaml +++ b/glide.yaml @@ -20,6 +20,8 @@ import: subpackages: - tools/clientcmd - tools/clientcmd/api +- package: github.com/spf13/afero + version: ^1.1.0 testImport: - package: github.com/go-test/deep version: v1.0.0