Skip to content

Commit

Permalink
Added output structure to hold oidc token verifier output data.
Browse files Browse the repository at this point in the history
Added methods to set the values in output struct and method to write the value as json file.
Added supporting cli flag, interface, mocks and tests.
  • Loading branch information
dekiel committed Jan 10, 2025
1 parent bcd0320 commit 279a0e3
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 4 deletions.
8 changes: 7 additions & 1 deletion .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ packages:
config:
all: True
dir: "{{.InterfaceDir}}/mocks"
outpkg: "{{.PackageName}}mocks"
outpkg: "{{.PackageName}}mocks"
github.com/kyma-project/test-infra/cmd/oidc-token-verifier:
config:
dir: "{{.InterfaceDir}}/mocks"
outpkg: "{{.PackageName}}mocks"
interfaces:
TrustedIssuerProvider:
86 changes: 84 additions & 2 deletions cmd/oidc-token-verifier/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"encoding/json"
"fmt"
"os"

Expand All @@ -25,6 +26,7 @@ type options struct {
trustedWorkflows []string
debug bool
oidcTokenExpirationTime int // OIDC token expiration time in minutes
outputPath string
}

var (
Expand All @@ -50,6 +52,7 @@ func NewRootCmd() *cobra.Command {
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().BoolVarP(&opts.debug, "debug", "d", false, "Enable debug mode")
rootCmd.PersistentFlags().IntVarP(&opts.oidcTokenExpirationTime, "oidc-token-expiration-time", "e", 10, "OIDC token expiration time in minutes")
rootCmd.PersistentFlags().StringVarP(&opts.outputPath, "output-path", "o", "/oidc-verifier-output.json", "Path to the file where the output data will be saved")
return rootCmd
}

Expand All @@ -74,6 +77,67 @@ func init() {
rootCmd.AddCommand(verifyCmd)
}

type TrustedIssuerProvider interface {
GetIssuer() tioidc.Issuer
}

// output is a struct that holds the output values that are printed to the file.
// The data provided in this struct is relevant for the component that uses the OIDC token verifier.
// The output values are printed to the file in the json format.
type output struct {
GithubURL string
ClientID string
}

// setGithubURLOutput sets the Github URL value to the output struct.
// The Github URL value is read from the TokenProcessor trusted issuer.
func (output *output) setGithubURLOutput(logger Logger, issuerProvider TrustedIssuerProvider) error {
var githubURL string

if githubURL = issuerProvider.GetIssuer().GetGithubURL(); githubURL == "" {
return fmt.Errorf("github URL not found in the tokenProcessor trusted issuer: %s", issuerProvider.GetIssuer())
}

output.GithubURL = githubURL

logger.Debugw("Set output Github URL value", "githubURL", output.GithubURL)

return nil
}

// setClientIDOutput sets the client ID value to the output struct.
// The client ID value is read from the TokenProcessor trusted issuer.
func (output *output) setClientIDOutput(logger Logger, issuerProvider TrustedIssuerProvider) error {
var clientID string
if clientID = issuerProvider.GetIssuer().ClientID; clientID == "" {
return fmt.Errorf("client ID not found in the tokenProcessor trusted issuer: %s", issuerProvider.GetIssuer())
}

output.ClientID = clientID

logger.Debugw("Set output client ID value", "clientID", output.ClientID)

return nil
}

// writeOutputFile writes the output values to the json file.
// The file path is specified by the --output-path flag.
func (output *output) writeOutputFile(logger Logger, path string) error {
outputFile, err := os.Create(path)
if err != nil {
return err
}

err = json.NewEncoder(outputFile).Encode(output)
if err != nil {
return err
}

logger.Debugw("Output values written to the file", "path", path, "output", output)

return nil
}

// isTokenProvided checks if the token flag is set.
// If not, check if AUTHORIZATION environment variable is set.
// If neither is set, return an error.
Expand Down Expand Up @@ -140,7 +204,6 @@ func (opts *options) verifyToken() error {
return err
}
logger.Infow("Token processor created for trusted issuer", "issuer", tokenProcessor.Issuer())
fmt.Printf("GITHUB_URL=%s\n", tokenProcessor.GetIssuer().GetGithubURL())

ctx := context.Background()
// Create a new provider using OIDC discovery to get the public keys.
Expand Down Expand Up @@ -176,14 +239,33 @@ func (opts *options) verifyToken() error {
if err != nil {
return err
}

logger.Infow("Token claims expectations verified successfully")
logger.Infow("All token checks passed successfully")

outputData := output{}
err = outputData.setGithubURLOutput(logger, &tokenProcessor)
if err != nil {
return err
}

err = outputData.setClientIDOutput(logger, &tokenProcessor)
if err != nil {
return err
}

err = outputData.writeOutputFile(logger, opts.outputPath)
if err != nil {
return err
}

logger.Infow("Output data written to the file", "path", opts.outputPath)

return nil
}

func main() {
if err := rootCmd.Execute(); err != nil {
panic(err)
}
}
}
129 changes: 129 additions & 0 deletions cmd/oidc-token-verifier/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package main

import (
"encoding/json"
"fmt"
"os"

"github.com/kyma-project/test-infra/cmd/oidc-token-verifier/mocks"
tioidc "github.com/kyma-project/test-infra/pkg/oidc"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/zap"
)

var _ = Describe("Output", func() {
var (
logger Logger
issuerProvider *mainmocks.MockTrustedIssuerProvider
issuer tioidc.Issuer
out output
)

BeforeEach(func() {
logger = zap.NewNop().Sugar()
issuerProvider = &mainmocks.MockTrustedIssuerProvider{}
issuer = tioidc.Issuer{
Name: "test-issuer",
IssuerURL: "https://test-issuer.com",
JWKSURL: "https://test-issuer.com/jwks",
ExpectedJobWorkflowRef: "test-workflow",
GithubURL: "https://github-test.com",
ClientID: "test-client-id",
}
out = output{}
})

Describe("setGithubURLOutput", func() {
Context("when the Github URL is found in the tokenProcessor trusted issuer", func() {
It("should set the Github URL in the output struct", func() {
issuerProvider.On("GetIssuer").Return(issuer)

err := out.setGithubURLOutput(logger, issuerProvider)
Expect(err).NotTo(HaveOccurred(), "Expected no error, but got: %v", err)
Expect(out.GithubURL).To(Equal(issuer.GithubURL), "Expected Github URL to be %s, but got %s", issuer.GithubURL, out.GithubURL)
})
})

Context("when the Github URL is not found in the tokenProcessor trusted issuer", func() {
It("should return an error", func() {
issuer.GithubURL = ""
issuerProvider.On("GetIssuer").Return(issuer)

err := out.setGithubURLOutput(logger, issuerProvider)
Expect(err).To(HaveOccurred(), "Expected an error, but got none")
Expect(err).To(MatchError(fmt.Errorf("github URL not found in the tokenProcessor trusted issuer: %s", issuerProvider.GetIssuer())), "Expected error message to be 'github URL not found in the tokenProcessor trusted issuer', but got %s", err)
Expect(out.GithubURL).To(BeEmpty(), "Expected Github URL to be empty, but got %v", out.GithubURL)
})
})
})

Describe("setClientIDOutput", func() {
Context("when the Client ID is found in the tokenProcessor trusted issuer", func() {
It("should set the Client ID in the output struct", func() {
issuerProvider.On("GetIssuer").Return(issuer)

err := out.setClientIDOutput(logger, issuerProvider)
Expect(err).NotTo(HaveOccurred(), "Expected no error, but got: %v", err)
Expect(out.ClientID).To(Equal(issuer.ClientID), "Expected Client ID to be %s, but got %s", issuer.ClientID, out.ClientID)
})
})

Context("when the Client ID is not found in the tokenProcessor trusted issuer", func() {
It("should return an error", func() {
issuer.ClientID = ""
issuerProvider.On("GetIssuer").Return(issuer)

err := out.setClientIDOutput(logger, issuerProvider)
Expect(err).To(HaveOccurred(), "Expected an error, but got none")
Expect(err).To(MatchError(fmt.Errorf("client ID not found in the tokenProcessor trusted issuer: %s", issuerProvider.GetIssuer())), "Expected error message to be 'client ID not found in the tokenProcessor trusted issuer', but got %s", err)
Expect(out.ClientID).To(BeEmpty(), "Expected Client ID to be empty, but got %v", out.ClientID)
})
})
})

Describe("writeOutputFile", func() {
var filePath = "./output.json"

BeforeEach(func() {
// Verify if the path exists and is writable
file, err := os.Create(filePath)
Expect(err).NotTo(HaveOccurred(), "Expected no error creating the file, but got: %v", err)
file.Close()
})

AfterEach(func() {
// Remove created artifacts
err := os.Remove(filePath)
Expect(err).NotTo(HaveOccurred(), "Expected no error removing the file, but got: %v", err)
})

Context("when the output file is successfully written", func() {
It("should write the output values to the json file", func() {
out.GithubURL = issuer.GithubURL
out.ClientID = issuer.ClientID

err := out.writeOutputFile(logger, filePath)
Expect(err).NotTo(HaveOccurred(), "Expected no error, but got: %v", err)

file, err := os.Open(filePath)
Expect(err).NotTo(HaveOccurred(), "Expected no error opening the file, but got: %v", err)
defer file.Close()

var writtenOutput output
err = json.NewDecoder(file).Decode(&writtenOutput)
Expect(err).NotTo(HaveOccurred(), "Expected no error decoding the file, but got: %v", err)
Expect(writtenOutput).To(Equal(out), "Expected written output to be %v, but got %v", out, writtenOutput)
})
})

Context("when there is an error creating the output file", func() {
It("should return an error", func() {
filePath := "/invalid-path/output.json"

err := out.writeOutputFile(logger, filePath)
Expect(err).To(HaveOccurred(), "Expected an error, but got none")
})
})
})
})
80 changes: 80 additions & 0 deletions cmd/oidc-token-verifier/mocks/mock_TrustedIssuerProvider.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions cmd/oidc-token-verifier/oidc_token_verifier_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestOidcTokenVerifier(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "OidcTokenVerifier Suite")
}
3 changes: 2 additions & 1 deletion pkg/oidc/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type Issuer struct {
JWKSURL string `json:"jwks_url" yaml:"jwks_url"`
ExpectedJobWorkflowRef string `json:"expected_job_workflow_ref" yaml:"expected_job_workflow_ref"`
GithubURL string `json:"github_url" yaml:"github_url"`
ClientID string `json:"client_id" yaml:"client_id"`
}

func (i Issuer) GetGithubURL() string {
Expand Down Expand Up @@ -486,4 +487,4 @@ func (tokenProcessor *TokenProcessor) ValidateClaims(claims ClaimsInterface, tok
return fmt.Errorf("expecations validation failed: %w", err)
}
return nil
}
}

0 comments on commit 279a0e3

Please sign in to comment.