Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OIDC verifier docs #11024

Merged
merged 12 commits into from
Jun 24, 2024
2 changes: 1 addition & 1 deletion cmd/image-builder/github-workflow-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,4 @@ reliable infrastructure for the building of OCI images when the pipeline is trig
The Image Builder solution, with its seamless integration with GitHub workflows and Azure DevOps pipeline, offers developers a robust and
secure method to incorporate the building of OCI images into their workflows. By leveraging a signed JWT format in which an OIDC token from
GitHub's OIDC identity provider is passed, it ensures the secure and authorized passing of information about the workflow and the image to
build. The entire build process adheres to SLC-29 compliance, providing a reliable infrastructure for the building of OCI images.
build. The entire build process adheres to SLC-29 compliance, providing a reliable infrastructure for the building of OCI images.
69 changes: 69 additions & 0 deletions cmd/oidc-token-verifier/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# OIDC Token Verifier

The OIDC Token Verifier is a command-line tool designed to validate the OIDC token and its claim values. It is primarily used in the
oci-image-builder Azure DevOps pipeline to authenticate and ensure the integrity of the token passed to the pipeline.

At present, the tool supports only the GitHub.com OIDC identity provider and the RS256 algorithm for verifying the token signature.
dekiel marked this conversation as resolved.
Show resolved Hide resolved

## How to Use
dekiel marked this conversation as resolved.
Show resolved Hide resolved

Run the OIDC Token Verifier passing a raw OIDC token in the `token` flag or in the `AUTHORIZATION` environment variable.
Token passed in the `token` flag will take precedence over the token passed in the `AUTHORIZATION` environment variable.
dekiel marked this conversation as resolved.
Show resolved Hide resolved

```bash
oidc-token-verifier --token "your-oidc-token"
```

See all available [flags](https://github.com/kyma-project/test-infra/blob/main/cmd/oidc-token-verifier/main.go#L45-L55).

> [!IMPORTANT]
> If a token is issued by the trusted issuer, the tool will validate the token against it.
> If the token is valid and the claims are as expected, the tool will exit with a status code of 0.
> Otherwise, it will exit with a status code of 1.
dekiel marked this conversation as resolved.
Show resolved Hide resolved

Apart from standard OIDC token validation, the tool will validate the following claim values:
dekiel marked this conversation as resolved.
Show resolved Hide resolved

- `iss` - the issuer of the token
- `aud` - the audience of the token
- `job_workflow_ref` - the reference of the GitHub reusable workflow used in the calling GitHub workflow
dekiel marked this conversation as resolved.
Show resolved Hide resolved

> [!IMPORTANT]
> Trusted issuer and allowed workflow reference are hardcoded in the tool.
dekiel marked this conversation as resolved.
Show resolved Hide resolved
> The issuer is set to `https://token.actions.githubusercontent.com`.
> The workflow reference is set to `kyma-project/test-infra/.github/workflows/image-builder.yml@refs/heads/main`
dekiel marked this conversation as resolved.
Show resolved Hide resolved
> This is a temporary solution and will be replaced with a more flexible configuration in the future.
> See [issue](https://github.com/kyma-project/test-infra/issues/11000) for more details.

## How it works
dekiel marked this conversation as resolved.
Show resolved Hide resolved

- the OIDC discovery
- the token and claims verification
- hardcoded trusted issuer and workflow, link to issue
dekiel marked this conversation as resolved.
Show resolved Hide resolved

The OIDC Token Verifier is designed to validate provided OIDC token and its claim values and provide a status code based on the validation
result.
dekiel marked this conversation as resolved.
Show resolved Hide resolved
The tool is not expected to be used as a long-running service but rather as a command-line tool that is run on demand as part of a larger
pipeline.
It reads the token issuer and verifies it against the trusted issuer. If the issuer is trusted, the tool proceeds to validate the token.
During the token validation, the tool uses the OIDC discovery to get the public key used to sign the token.
Once the token passes standard OIDC validation, the tool verifies the token claim values.
The tool verifies the following claim values:

- `job_workflow_ref` - the reference of GitHub reusable workflow used in the calling GitHub workflow,
it must match the value in `Issuer.ExpectedJobWorkflowRef` field of trusted issuer.
dekiel marked this conversation as resolved.
Show resolved Hide resolved

If the token is valid and all claim values are as expected, the tool will exit with a status code of 0, indicating that the token is valid.
Otherwise, it will exit with a status code of 1, indicating that the token is invalid.
dekiel marked this conversation as resolved.
Show resolved Hide resolved

### Activity Diagram

![oidc-token-verifier-activity-diagram](oidc-token-verifier-activity-diagram.svg)

## Use Case

### oci-image-builder Pipeline

The tool was developed to be used in the oci-image-builder pipeline, where it authenticates and authorizes calls that trigger the pipeline.
By verifying the tokens and validating its claims against allowed values, it prevents unauthorized or malicious image builds.
This is done by ensuring that the token is issued by the trusted issuer and that the token is used in the context of the trusted GitHub
workflow reference.
26 changes: 20 additions & 6 deletions cmd/oidc-token-verifier/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ func NewRootCmd() *cobra.Command {
Long: `oidc is a CLI tool to verify OIDC tokens and extract claims from them. It can use cached public keys to verify tokens.
It uses OIDC discovery to get the public keys and verify the token whenever the public keys are not cached or expired.`,
}
rootCmd.PersistentFlags().StringVarP(&opts.token, "token", "t", "", "OIDC token")
rootCmd.PersistentFlags().StringVarP(&opts.token, "token", "t", "", "OIDC token to verify")
rootCmd.PersistentFlags().StringVarP(&opts.newPublicKeysVarName, "new-keys-var", "n", "OIDC_NEW_PUBLIC_KEYS", "Name of the environment variable to set when new public keys are fetched")
// This flag should be enabled once we add support for it in the code.
// rootCmd.PersistentFlags().StringSliceVarP(&opts.trustedWorkflows, "trusted-workflows", "w", []string{}, "List of trusted workflows")
// err := rootCmd.MarkPersistentFlagRequired("trusted-workflows")
// if err != nil {
// panic(err)
// }
rootCmd.PersistentFlags().StringVarP(&opts.clientID, "client-id", "c", "image-builder", "OIDC token client ID")
rootCmd.PersistentFlags().StringVarP(&opts.clientID, "client-id", "c", "image-builder", "OIDC token client ID, this is used to verify the audience claim in the token. The value should be the same as the audience claim value in the token.")
rootCmd.PersistentFlags().StringVarP(&opts.publicKeyPath, "public-key-path", "p", "", "Path to the cached public keys directory")
rootCmd.PersistentFlags().BoolVarP(&opts.debug, "debug", "d", false, "Enable debug mode")
return rootCmd
Expand All @@ -77,10 +77,10 @@ func init() {
rootCmd.AddCommand(verifyCmd)
}

// isTokenProvided checks if the token flag is set.
// If not, check if AUTHORIZATION environment variable is set.
// If neither is set, return an error.
func isTokenProvided(logger Logger, opts *options) error {
// Check if a token flag is set.
// If not, check if AUTHORIZATION environment variable is set.
// If neither is set, return an error.
if opts.token == "" {
logger.Infow("Token flag not provided, checking for AUTHORIZATION environment variable")
opts.token = os.Getenv("AUTHORIZATION")
Expand All @@ -100,7 +100,7 @@ func isTokenProvided(logger Logger, opts *options) error {
// It returns an error if the token validation failed.
// It verifies the token signature and expiration time, verifies if the token is issued by a trusted issuer,
// and the claims have expected values.
// It uses OIDC discovery to get the public keys.
// It uses OIDC discovery to get the identity provider public keys.
func (opts *options) extractClaims() error {
var (
zapLogger *zap.Logger
Expand Down Expand Up @@ -128,12 +128,17 @@ func (opts *options) extractClaims() error {
logger.Infow("Using the following new public keys environment variable", "new-keys-var", opts.newPublicKeysVarName)
logger.Infow("Using the following claims output path", "claims-output-path", opts.outputPath)

// Create a new verifier config that will be used to verify the token.
// The clientID is used to verify the audience claim in the token.
verifyConfig, err := tioidc.NewVerifierConfig(logger, opts.clientID)
if err != nil {
return err
}
logger.Infow("Verifier config created", "config", verifyConfig)

// Create a new token processor
// It reads issuer from the token and verifies if the issuer is trusted.
// The tokenProcessor is a main object that is used to verify the token and extract the claim values.
// TODO(dekiel): add support for providing trusted issuers instead of using the value from the package.
tokenProcessor, err := tioidc.NewTokenProcessor(logger, tioidc.TrustedOIDCIssuers, opts.token, verifyConfig)
if err != nil {
Expand All @@ -142,16 +147,25 @@ func (opts *options) extractClaims() error {
logger.Infow("Token processor created for trusted issuer", "issuer", tokenProcessor.Issuer())

ctx := context.Background()
// Create a new provider using OIDC discovery to get the public keys.
// It uses the issuer from the token to get the OIDC discovery endpoint.
provider, err := tioidc.NewProviderFromDiscovery(ctx, logger, tokenProcessor.Issuer())
if err != nil {
return err
}
logger.Infow("Provider created using OIDC discovery", "issuer", tokenProcessor.Issuer())

// Create a new verifier using the provider and the verifier config.
// The verifier is used to verify the token signature, expiration time and execute standard OIDC validation.
verifier := provider.NewVerifier(logger, verifyConfig)
logger.Infow("New verifier created")

// claims will store the extracted claim values from the token.
claims := tioidc.NewClaims(logger)
// Verifies the token and check if the claims have expected values.
// Verifies custom claim values too.
// Extract the claim values from the token into the claims struct.
// It provides a final result if the token is valid and the claims have expected values.
err = tokenProcessor.VerifyAndExtractClaims(ctx, &verifier, &claims)
if err != nil {
return err
Expand Down
Loading
Loading