Skip to content

Commit

Permalink
Add support for Github OIDC
Browse files Browse the repository at this point in the history
This adds support for handing Github's OIDC tokens in addition to Google and SPIFFE.

Github OIDC tokens look something like:

```json
{
  "jti": "0687e989-80d6-42b0-a498-c65e99315f37",
  "sub": "repo:mattmoor/stupid-example:ref:refs/heads/main",
  "aud": "sigstore",
  "ref_protected": "false",
  "job_workflow_ref": "mattmoor/stupid-example/.github/workflows/my-action.yaml@refs/heads/main",
  "iss": "https://vstoken.actions.githubusercontent.com",
  "nbf": 1631210221,
  "exp": 1631211121,
  "iat": 1631210821
}
```

This change verifies things against the `iss` endpoint, and encodes the
`job_workflow_ref` into the x509 cert as a URI by prefixing it as:
```
https://github.com/{job_workflow_ref}
```

I verified this works with a local Fulcio setup and some identity tokens I
exfiltrated from actions for the test.  The major caveat was that I had
to tweak more than I'd have liked to for my test because things currently
use the v1beta1 API, and I had to rejigger things to use v1 for my local
test.

I chatted a bunch with `@dlorenc` about v1 migration, and the major concern
is the backwards compatibility with the current Fulcio cert, so these
changes have those pieces backed out.
  • Loading branch information
mattmoor committed Sep 10, 2021
1 parent 286c4f2 commit 161467a
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 5 deletions.
2 changes: 2 additions & 0 deletions pkg/api/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ func Subject(ctx context.Context, tok *oidc.IDToken, cfg config.FulcioConfig, pu
return challenges.Email(ctx, tok, publicKey, challenge)
case config.IssuerTypeSpiffe:
return challenges.Spiffe(ctx, tok, publicKey, challenge)
case config.IssuerTypeGithubWorkflow:
return challenges.GithubWorkflow(ctx, tok, publicKey, challenge)
default:
return nil, fmt.Errorf("unsupported issuer: %s", iss.Type)
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/api/googleca_signing_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ func GoogleCASigningCertHandler(ctx context.Context, subj *challenges.ChallengeR
privca = googleca.EmailSubject(subj.Value)
case challenges.SpiffeValue:
privca = googleca.SpiffeSubject(subj.Value)

case challenges.GithubWorkflowValue:
privca = googleca.GithubWorkflowSubject(subj.Value)
}
req := googleca.Req(parent, privca, publicKey)
log.Logger.Infof("requesting cert from %s for %v", parent, Subject)
Expand Down
8 changes: 8 additions & 0 deletions pkg/ca/googleca/googleca.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,11 @@ func SpiffeSubject(id string) *privatecapb.CertificateConfig_SubjectConfig {
},
}
}

func GithubWorkflowSubject(id string) *privatecapb.CertificateConfig_SubjectConfig {
return &privatecapb.CertificateConfig_SubjectConfig{
SubjectAltName: &privatecapb.SubjectAltNames{
Uris: []string{id},
},
}
}
53 changes: 51 additions & 2 deletions pkg/challenges/challenges.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"errors"
"fmt"
"net/url"
"strconv"
"strings"

"github.com/sigstore/fulcio/pkg/config"
Expand All @@ -38,6 +39,7 @@ type ChallengeType int
const (
EmailValue ChallengeType = iota
SpiffeValue
GithubWorkflowValue
)

type ChallengeResult struct {
Expand Down Expand Up @@ -81,7 +83,10 @@ func Email(ctx context.Context, principal *oidc.IDToken, pubKey, challenge []byt
}

// Now issue cert!
return &ChallengeResult{EmailValue, emailAddress}, nil
return &ChallengeResult{
TypeVal: EmailValue,
Value: emailAddress,
}, nil
}

func Spiffe(ctx context.Context, principal *oidc.IDToken, pubKey, challenge []byte) (*ChallengeResult, error) {
Expand Down Expand Up @@ -111,7 +116,51 @@ func Spiffe(ctx context.Context, principal *oidc.IDToken, pubKey, challenge []by
}

// Now issue cert!
return &ChallengeResult{SpiffeValue, spiffeID}, nil
return &ChallengeResult{
TypeVal: SpiffeValue,
Value: spiffeID,
}, nil
}

func GithubWorkflow(ctx context.Context, principal *oidc.IDToken, pubKey, challenge []byte) (*ChallengeResult, error) {
workflowRef, _, err := workflowFromIDToken(principal)
if err != nil {
return nil, err
}

pkixPubKey, err := x509.ParsePKIXPublicKey(pubKey)
if err != nil {
return nil, err
}

// Check the proof
if err := CheckSignature(pkixPubKey, challenge, principal.Subject); err != nil {
return nil, err
}

// Now issue cert!
return &ChallengeResult{
TypeVal: GithubWorkflowValue,
Value: workflowRef,
}, nil
}

func workflowFromIDToken(token *oidc.IDToken) (string, bool, error) {
// Extract custom claims
var claims struct {
JobWorkflowRef string `json:"job_workflow_ref"`
RefProtected string `json:"ref_protected"`
}
if err := token.Claims(&claims); err != nil {
return "", false, err
}
rp, err := strconv.ParseBool(claims.RefProtected)
if err != nil {
return "", false, err
}

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

func isSpiffeIDAllowed(host, spiffeID string) bool {
Expand Down
10 changes: 8 additions & 2 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ type OIDCIssuer struct {
type IssuerType string

const (
IssuerTypeEmail = "email"
IssuerTypeSpiffe = "spiffe"
IssuerTypeEmail = "email"
IssuerTypeGithubWorkflow = "github-workflow"
IssuerTypeSpiffe = "spiffe"
)

func ParseConfig(b []byte) (FulcioConfig, error) {
Expand All @@ -60,6 +61,11 @@ var DefaultConfig = FulcioConfig{
ClientID: "sigstore",
Type: IssuerTypeEmail,
},
"https://vstoken.actions.githubusercontent.com": {
IssuerURL: "https://vstoken.actions.githubusercontent.com",
ClientID: "sigstore",
Type: IssuerTypeGithubWorkflow,
},
},
}

Expand Down

0 comments on commit 161467a

Please sign in to comment.