Skip to content

Commit

Permalink
Move kubernetes principal to package (#619)
Browse files Browse the repository at this point in the history
Signed-off-by: Nathan Smith <nathan@chainguard.dev>
  • Loading branch information
nsmith5 authored May 27, 2022
1 parent 3a1f587 commit f1a874d
Show file tree
Hide file tree
Showing 4 changed files with 344 additions and 88 deletions.
58 changes: 2 additions & 56 deletions pkg/challenges/challenges.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/sigstore/fulcio/pkg/config"
"github.com/sigstore/fulcio/pkg/identity"
"github.com/sigstore/fulcio/pkg/identity/github"
"github.com/sigstore/fulcio/pkg/identity/kubernetes"
"github.com/sigstore/fulcio/pkg/identity/spiffe"

"github.com/coreos/go-oidc/v3/oidc"
Expand All @@ -41,7 +42,6 @@ type ChallengeType int

const (
EmailValue ChallengeType = iota
KubernetesValue
URIValue
UsernameValue
)
Expand All @@ -67,12 +67,6 @@ func (cr *ChallengeResult) Embed(ctx context.Context, cert *x509.Certificate) er
switch cr.TypeVal {
case EmailValue:
cert.EmailAddresses = []string{cr.Value}
case KubernetesValue:
k8sURI, err := url.Parse(cr.Value)
if err != nil {
return err
}
cert.URIs = []*url.URL{k8sURI}
case URIValue:
subjectURI, err := url.Parse(cr.Value)
if err != nil {
Expand Down Expand Up @@ -133,20 +127,6 @@ func email(ctx context.Context, principal *oidc.IDToken) (identity.Principal, er
}, nil
}

func kubernetes(ctx context.Context, principal *oidc.IDToken) (identity.Principal, error) {
k8sURI, err := kubernetesToken(principal)
if err != nil {
return nil, err
}

return &ChallengeResult{
Issuer: principal.Issuer,
TypeVal: KubernetesValue,
Value: k8sURI,
subject: principal.Subject,
}, nil
}

func uri(ctx context.Context, principal *oidc.IDToken) (identity.Principal, error) {
uriWithSubject := principal.Subject

Expand Down Expand Up @@ -201,40 +181,6 @@ func username(ctx context.Context, principal *oidc.IDToken) (identity.Principal,
}, nil
}

func kubernetesToken(token *oidc.IDToken) (string, error) {
// Extract custom claims
var claims struct {
// "kubernetes.io": {
// "namespace": "default",
// "pod": {
// "name": "oidc-test",
// "uid": "49ad3572-b3dd-43a6-8d77-5858d3660275"
// },
// "serviceaccount": {
// "name": "default",
// "uid": "f5720c1d-e152-4356-a897-11b07aff165d"
// }
// }
Kubernetes struct {
Namespace string `json:"namespace"`
Pod struct {
Name string `json:"name"`
UID string `json:"uid"`
} `json:"pod"`
ServiceAccount struct {
Name string `json:"name"`
UID string `json:"uid"`
} `json:"serviceaccount"`
} `json:"kubernetes.io"`
}
if err := token.Claims(&claims); err != nil {
return "", err
}

// We use this in URIs, so it has to be a URI.
return "https://kubernetes.io/namespaces/" + claims.Kubernetes.Namespace + "/serviceaccounts/" + claims.Kubernetes.ServiceAccount.Name, nil
}

func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Principal, error) {
iss, ok := config.FromContext(ctx).GetIssuer(tok.Issuer)
if !ok {
Expand All @@ -250,7 +196,7 @@ func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Prin
case config.IssuerTypeGithubWorkflow:
principal, err = github.WorkflowPrincipalFromIDToken(ctx, tok)
case config.IssuerTypeKubernetes:
principal, err = kubernetes(ctx, tok)
principal, err = kubernetes.PrincipalFromIDToken(ctx, tok)
case config.IssuerTypeURI:
principal, err = uri(ctx, tok)
case config.IssuerTypeUsername:
Expand Down
32 changes: 0 additions & 32 deletions pkg/challenges/challenges_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,38 +65,6 @@ func TestEmbedChallengeResult(t *testing.T) {
`Certificate should have issuer extension set`: factIssuerIs("example.com"),
},
},
`Good Kubernetes value`: {
Challenge: ChallengeResult{
Issuer: `k8s.example.com`,
TypeVal: KubernetesValue,
Value: "https://k8s.example.com",
},
WantErr: false,
WantFacts: map[string]func(x509.Certificate) error{
`Issuer is k8s.example.com`: factIssuerIs(`k8s.example.com`),
`SAN is https://k8s.example.com`: func(cert x509.Certificate) error {
WantURI, err := url.Parse("https://k8s.example.com")
if err != nil {
return err
}
if len(cert.URIs) != 1 {
return errors.New("no URI SAN set")
}
if diff := cmp.Diff(cert.URIs[0], WantURI); diff != "" {
return errors.New(diff)
}
return nil
},
},
},
`Kubernetes value with bad URL fails`: {
Challenge: ChallengeResult{
Issuer: `example.com`,
TypeVal: KubernetesValue,
Value: "\nbadurl",
},
WantErr: true,
},
`Good URI value`: {
Challenge: ChallengeResult{
Issuer: `foo.example.com`,
Expand Down
104 changes: 104 additions & 0 deletions pkg/identity/kubernetes/principal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2022 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubernetes

import (
"context"
"crypto/x509"
"net/url"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/sigstore/fulcio/pkg/ca/x509ca"
"github.com/sigstore/fulcio/pkg/identity"
)

type principal struct {
// Subject ('sub') from ID token
subject string

// Issuer ('iss') from ID token
issuer string

// URI to be set in certificate. URI is of the form
// https://kubernetes.io/namespaces/<namespace>/serviceaccounts/<serviceaccount>.
uri string
}

func PrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) {
k8sURI, err := kubernetesToken(token)
if err != nil {
return nil, err
}
return principal{
subject: token.Subject,
issuer: token.Issuer,
uri: k8sURI,
}, nil
}

func (p principal) Name(context.Context) string {
return p.subject
}

func (p principal) Embed(ctx context.Context, cert *x509.Certificate) error {
parsed, err := url.Parse(p.uri)
if err != nil {
return err
}
cert.URIs = []*url.URL{parsed}

cert.ExtraExtensions, err = x509ca.Extensions{
Issuer: p.issuer,
}.Render()
if err != nil {
return err
}

return nil
}

func kubernetesToken(token *oidc.IDToken) (string, error) {
// Extract custom claims
var claims struct {
// "kubernetes.io": {
// "namespace": "default",
// "pod": {
// "name": "oidc-test",
// "uid": "49ad3572-b3dd-43a6-8d77-5858d3660275"
// },
// "serviceaccount": {
// "name": "default",
// "uid": "f5720c1d-e152-4356-a897-11b07aff165d"
// }
// }
Kubernetes struct {
Namespace string `json:"namespace"`
Pod struct {
Name string `json:"name"`
UID string `json:"uid"`
} `json:"pod"`
ServiceAccount struct {
Name string `json:"name"`
UID string `json:"uid"`
} `json:"serviceaccount"`
} `json:"kubernetes.io"`
}
if err := token.Claims(&claims); err != nil {
return "", err
}

// We use this in URIs, so it has to be a URI.
return "https://kubernetes.io/namespaces/" + claims.Kubernetes.Namespace + "/serviceaccounts/" + claims.Kubernetes.ServiceAccount.Name, nil
}
Loading

0 comments on commit f1a874d

Please sign in to comment.