From fbeadfdaa5566f6d23286781fd6ff25e8dcfc233 Mon Sep 17 00:00:00 2001 From: Przemek Pokrywka <12400578+dekiel@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:58:07 +0200 Subject: [PATCH] OIDC verifier docs (#11024) * draft 0.1 * bulletpoints to include * Documentation first version * More code comments. * Better describe tokenProcessor role * With activity diagram. * Apply suggestions from code review Co-authored-by: Patryk Dobrowolski * using important baner * Use .svg diagram. * Apply suggestions from code review Co-authored-by: Iwona Langer * Removed notes list. --------- Co-authored-by: Patryk Dobrowolski Co-authored-by: Iwona Langer --- .../github-workflow-integration.md | 2 +- cmd/oidc-token-verifier/README.md | 65 ++ cmd/oidc-token-verifier/main.go | 26 +- .../oidc-token-verifier-activity-diagram.svg | 581 ++++++++++++++++++ pkg/oidc/oidc.go | 1 + 5 files changed, 668 insertions(+), 7 deletions(-) create mode 100644 cmd/oidc-token-verifier/README.md create mode 100644 cmd/oidc-token-verifier/oidc-token-verifier-activity-diagram.svg diff --git a/cmd/image-builder/github-workflow-integration.md b/cmd/image-builder/github-workflow-integration.md index 52fab7d0a70c..a24fa14f505c 100644 --- a/cmd/image-builder/github-workflow-integration.md +++ b/cmd/image-builder/github-workflow-integration.md @@ -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. \ No newline at end of file diff --git a/cmd/oidc-token-verifier/README.md b/cmd/oidc-token-verifier/README.md new file mode 100644 index 000000000000..c8e1d81afb23 --- /dev/null +++ b/cmd/oidc-token-verifier/README.md @@ -0,0 +1,65 @@ +# 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. + +## Usage + +Run the OIDC Token Verifier passing a raw OIDC token in the `token` flag or in the **AUTHORIZATION** environment variable. +The token passed in the `token` flag will take precedence over the token passed in the **AUTHORIZATION** environment variable. + +```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 the trusted issuer issues a token, the tool validates the token against it. +> If the token is valid and the claims are as expected, the tool exits with the status code of `0`. +> Otherwise, it exits with the status code of `1`. + +Apart from standard OIDC token validation, the tool validates the following claim values: + +- **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 + +> [!IMPORTANT] +> The trusted issuer and allowed workflow reference are hardcoded in the tool. +> 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`. +> 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 + +The OIDC Token Verifier is designed to validate the provided OIDC token and its claim values and provide a status code based on the validation +result. +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 the **Issuer.ExpectedJobWorkflowRef** field of the trusted issuer. + +If the token is valid and all claim values are as expected, the tool exits with the status code of `0`, indicating that the token is valid. +Otherwise, it exits with the status code of `1`, indicating that the token is invalid. + +### 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. \ No newline at end of file diff --git a/cmd/oidc-token-verifier/main.go b/cmd/oidc-token-verifier/main.go index df4887719b14..e66fe329cb34 100644 --- a/cmd/oidc-token-verifier/main.go +++ b/cmd/oidc-token-verifier/main.go @@ -42,7 +42,7 @@ 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") @@ -50,7 +50,7 @@ func NewRootCmd() *cobra.Command { // 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 @@ -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") @@ -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 @@ -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 { @@ -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 diff --git a/cmd/oidc-token-verifier/oidc-token-verifier-activity-diagram.svg b/cmd/oidc-token-verifier/oidc-token-verifier-activity-diagram.svg new file mode 100644 index 000000000000..8771909bfeef --- /dev/null +++ b/cmd/oidc-token-verifier/oidc-token-verifier-activity-diagram.svg @@ -0,0 +1,581 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ options.token != "" +
+
+
+
+ +
+
+
+ + + + + + + + +
+
+
+ options.token == "" +
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + +
+
+
+ Create Verifier config. +
+
+
+
+ +
+
+
+ + + + + + + + +
+
+
+ token.issuer is not trusted +
+
+
+
+ +
+
+
+ + + + + + + + +
+
+
+ token.issuer is trusted +
+
+
+
+ +
+
+
+ + + + + + + + + + + +
+
+
+ token verification failed +
+
+
+
+ +
+
+
+ + + + + + + + +
+
+
+ token verification succeded +
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ assign AUTHORIZATION env value to options.token +
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + +
+
+
+ options.token != "" +
+
+
+
+ +
+
+
+ + + + + + + + +
+
+
+ options.token == "" +
+
+
+
+ +
+
+
+ + + + + + + + + + + +
+
+
+ Create TokenProcessor with options.token and verifier config. +
+
+
+
+ +
+
+
+ + + + + + + + + + + +
+
+
+ Create OIDC provider for token issuer using oidc discovery mechanism. +
+
+
+
+ +
+
+
+ + + + + + + + + + + +
+
+
+ Create verifier for provider with verifier config. +
+
+
+
+ +
+
+
+ + + + + + + + + + + +
+
+
+ Verify token using standard oidc verification steps +
+
+
+
+ +
+
+
+ + + + + + + + + + + +
+
+
+ Check if token issuer is trusted +
+
+
+
+ +
+
+
+ + + + + + + + + + + +
+
+
+ Extract token.claims to Claims struct +
+
+
+
+ +
+
+
+ + + + + + + + + + + +
+
+
+ Check if custom claims have expected values +
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ Read token issuer from not validated token. +
+
+
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/pkg/oidc/oidc.go b/pkg/oidc/oidc.go index 0530207b2886..0caadfb74e0c 100644 --- a/pkg/oidc/oidc.go +++ b/pkg/oidc/oidc.go @@ -196,6 +196,7 @@ func (tokenVerifier *TokenVerifier) Verify(ctx context.Context, rawToken string) return token, nil } +// Claims gets the claims from the token and unmarshal them into the provided claims struct. func (token *Token) Claims(claims interface{}) error { return token.Token.Claims(claims) }