Skip to content

Commit

Permalink
Move github principal to its own package
Browse files Browse the repository at this point in the history
Remove github workflow logic from challenge result and put it in its own
package.

Signed-off-by: Nathan Smith <nathan@chainguard.dev>
  • Loading branch information
Nathan Smith committed May 20, 2022
1 parent 00d7e66 commit 95dbb7f
Show file tree
Hide file tree
Showing 4 changed files with 363 additions and 149 deletions.
113 changes: 2 additions & 111 deletions pkg/challenges/challenges.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/sigstore/fulcio/pkg/ca/x509ca"
"github.com/sigstore/fulcio/pkg/config"
"github.com/sigstore/fulcio/pkg/identity"
"github.com/sigstore/fulcio/pkg/identity/github"
"github.com/spiffe/go-spiffe/v2/spiffeid"

"github.com/coreos/go-oidc/v3/oidc"
Expand All @@ -41,23 +42,13 @@ type ChallengeType int
const (
EmailValue ChallengeType = iota
SpiffeValue
GithubWorkflowValue
KubernetesValue
URIValue
UsernameValue
)

type AdditionalInfo int

// Additional information that can be added as a cert extension.
const (
GithubWorkflowTrigger AdditionalInfo = iota
GithubWorkflowSha
GithubWorkflowName
GithubWorkflowRepository
GithubWorkflowRef
)

type ChallengeResult struct {
Issuer string
TypeVal ChallengeType
Expand All @@ -66,9 +57,6 @@ type ChallengeResult struct {
// the certificate issued.
Value string

// Extra information from the token that can be added to extensions.
AdditionalInfo map[AdditionalInfo]string

// subject or email from the id token. This must be the thing
// signed in the proof of possession!
subject string
Expand All @@ -88,12 +76,6 @@ func (cr *ChallengeResult) Embed(ctx context.Context, cert *x509.Certificate) er
return err
}
cert.URIs = []*url.URL{challengeURL}
case GithubWorkflowValue:
jobWorkflowURI, err := url.Parse(cr.Value)
if err != nil {
return err
}
cert.URIs = []*url.URL{jobWorkflowURI}
case KubernetesValue:
k8sURI, err := url.Parse(cr.Value)
if err != nil {
Expand All @@ -113,29 +95,6 @@ func (cr *ChallengeResult) Embed(ctx context.Context, cert *x509.Certificate) er
exts := x509ca.Extensions{
Issuer: cr.Issuer,
}
if cr.TypeVal == GithubWorkflowValue {
var ok bool
exts.GithubWorkflowTrigger, ok = cr.AdditionalInfo[GithubWorkflowTrigger]
if !ok {
return errors.New("github workflow missing trigger claim")
}
exts.GithubWorkflowSHA, ok = cr.AdditionalInfo[GithubWorkflowSha]
if !ok {
return errors.New("github workflow missing SHA claim")
}
exts.GithubWorkflowName, ok = cr.AdditionalInfo[GithubWorkflowName]
if !ok {
return errors.New("github workflow missing workflow name claim")
}
exts.GithubWorkflowRepository, ok = cr.AdditionalInfo[GithubWorkflowRepository]
if !ok {
return errors.New("github workflow missing repository claim")
}
exts.GithubWorkflowRef, ok = cr.AdditionalInfo[GithubWorkflowRef]
if !ok {
return errors.New("github workflow missing ref claim")
}
}

var err error
cert.ExtraExtensions, err = exts.Render()
Expand Down Expand Up @@ -243,35 +202,6 @@ func kubernetes(ctx context.Context, principal *oidc.IDToken) (identity.Principa
}, nil
}

func githubWorkflow(ctx context.Context, principal *oidc.IDToken) (identity.Principal, error) {
workflowRef, err := workflowFromIDToken(principal)
if err != nil {
return nil, err
}
additionalInfo, err := workflowInfoFromIDToken(principal)
if err != nil {
return nil, err
}

cfg, ok := config.FromContext(ctx).GetIssuer(principal.Issuer)
if !ok {
return nil, errors.New("invalid configuration for OIDC ID Token issuer")
}

issuer, err := oauthflow.IssuerFromIDToken(principal, cfg.IssuerClaim)
if err != nil {
return nil, err
}

return &ChallengeResult{
Issuer: issuer,
TypeVal: GithubWorkflowValue,
Value: workflowRef,
AdditionalInfo: additionalInfo,
subject: principal.Subject,
}, nil
}

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

Expand Down Expand Up @@ -370,45 +300,6 @@ func kubernetesToken(token *oidc.IDToken) (string, error) {
return "https://kubernetes.io/namespaces/" + claims.Kubernetes.Namespace + "/serviceaccounts/" + claims.Kubernetes.ServiceAccount.Name, nil
}

func workflowFromIDToken(token *oidc.IDToken) (string, error) {
// Extract custom claims
var claims struct {
JobWorkflowRef string `json:"job_workflow_ref"`
// The other fields that are present here seem to depend on the type
// of workflow trigger that initiated the action.
}
if err := token.Claims(&claims); err != nil {
return "", err
}

// We use this in URIs, so it has to be a URI.
return "https://github.com/" + claims.JobWorkflowRef, nil
}

func workflowInfoFromIDToken(token *oidc.IDToken) (map[AdditionalInfo]string, error) {
// Extract custom claims
var claims struct {
Sha string `json:"sha"`
Trigger string `json:"event_name"`
Repository string `json:"repository"`
Workflow string `json:"workflow"`
Ref string `json:"ref"`
// The other fields that are present here seem to depend on the type
// of workflow trigger that initiated the action.
}
if err := token.Claims(&claims); err != nil {
return nil, err
}

// We use this in URIs, so it has to be a URI.
return map[AdditionalInfo]string{
GithubWorkflowSha: claims.Sha,
GithubWorkflowTrigger: claims.Trigger,
GithubWorkflowName: claims.Workflow,
GithubWorkflowRepository: claims.Repository,
GithubWorkflowRef: claims.Ref}, nil
}

func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Principal, error) {
iss, ok := config.FromContext(ctx).GetIssuer(tok.Issuer)
if !ok {
Expand All @@ -422,7 +313,7 @@ func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Prin
case config.IssuerTypeSpiffe:
principal, err = spiffe(ctx, tok)
case config.IssuerTypeGithubWorkflow:
principal, err = githubWorkflow(ctx, tok)
principal, err = github.WorkflowPrincipalFromIDToken(ctx, tok)
case config.IssuerTypeKubernetes:
principal, err = kubernetes(ctx, tok)
case config.IssuerTypeURI:
Expand Down
38 changes: 0 additions & 38 deletions pkg/challenges/challenges_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,44 +45,6 @@ func TestEmbedChallengeResult(t *testing.T) {
WantErr bool
WantFacts map[string]func(x509.Certificate) error
}{
`Github workflow challenge should have all Github workflow extensions and issuer set`: {
Challenge: ChallengeResult{
Issuer: `https://token.actions.githubusercontent.com`,
TypeVal: GithubWorkflowValue,
Value: `https://github.com/foo/bar/`,
AdditionalInfo: map[AdditionalInfo]string{
GithubWorkflowSha: "sha",
GithubWorkflowTrigger: "trigger",
GithubWorkflowName: "workflowname",
GithubWorkflowRepository: "repository",
GithubWorkflowRef: "ref",
},
},
WantErr: false,
WantFacts: map[string]func(x509.Certificate) error{
`Certifificate should have correct issuer`: factIssuerIs(`https://token.actions.githubusercontent.com`),
`Certificate has correct trigger extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 2}, "trigger"),
`Certificate has correct SHA extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 3}, "sha"),
`Certificate has correct workflow extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 4}, "workflowname"),
`Certificate has correct repository extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 5}, "repository"),
`Certificate has correct ref extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 6}, "ref"),
},
},
`Github workflow value with bad URL fails`: {
Challenge: ChallengeResult{
Issuer: `https://token.actions.githubusercontent.com`,
TypeVal: GithubWorkflowValue,
Value: "\nbadurl",
AdditionalInfo: map[AdditionalInfo]string{
GithubWorkflowSha: "sha",
GithubWorkflowTrigger: "trigger",
GithubWorkflowName: "workflowname",
GithubWorkflowRepository: "repository",
GithubWorkflowRef: "ref",
},
},
WantErr: true,
},
`Email challenges should set issuer extension and email subject`: {
Challenge: ChallengeResult{
Issuer: `example.com`,
Expand Down
95 changes: 95 additions & 0 deletions pkg/identity/github/principal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package github

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

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

type workflowPrincipal struct {
subject string
issuer string
url string
sha string
trigger string
repository string
workflow string
ref string
}

func WorkflowPrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) {
var claims struct {
JobWorkflowRef string `json:"job_workflow_ref"`
Sha string `json:"sha"`
Trigger string `json:"event_name"`
Repository string `json:"repository"`
Workflow string `json:"workflow"`
Ref string `json:"ref"`
}
if err := token.Claims(&claims); err != nil {
return nil, err
}

if claims.JobWorkflowRef == "" {
return nil, errors.New("missing job_workflow_ref claim in ID token")
}
if claims.Sha == "" {
return nil, errors.New("missing sha claim in ID token")
}
if claims.Trigger == "" {
return nil, errors.New("missing event_name claim in ID token")
}
if claims.Repository == "" {
return nil, errors.New("missing repository claim in ID token")
}
if claims.Workflow == "" {
return nil, errors.New("missing workflow claim in ID token")
}
if claims.Ref == "" {
return nil, errors.New("missing ref claim in ID token")
}

return &workflowPrincipal{
subject: token.Subject,
issuer: token.Issuer,
url: `https://github.com/` + claims.JobWorkflowRef,
sha: claims.Sha,
trigger: claims.Trigger,
repository: claims.Repository,
workflow: claims.Workflow,
ref: claims.Ref,
}, nil
}

func (w workflowPrincipal) Name(ctx context.Context) string {
return w.subject
}

func (w workflowPrincipal) Embed(ctx context.Context, cert *x509.Certificate) error {
// Set workflow URL to SubjectNameAlt on certificate
parsed, err := url.Parse(w.url)
if err != nil {
return err
}
cert.URIs = []*url.URL{parsed}

// Embed additional information into customer extensions
cert.ExtraExtensions, err = x509ca.Extensions{
Issuer: w.issuer,
GithubWorkflowTrigger: w.trigger,
GithubWorkflowSHA: w.sha,
GithubWorkflowName: w.workflow,
GithubWorkflowRepository: w.repository,
GithubWorkflowRef: w.ref,
}.Render()
if err != nil {
return err
}

return nil
}

0 comments on commit 95dbb7f

Please sign in to comment.