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

Add JWT-validation for idporten/maskinporten #589

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

larsore
Copy link
Contributor

@larsore larsore commented Jan 21, 2025

This PR extends idporten-spec and maskinporten-spec to include a new block called authentication. It can be enabled like this:

apiVersion: skiperator.kartverket.no/v1alpha1
kind: Application
metadata:
  name: test-client-with-auth
spec:
  image: image
  port: 8080
  ingresses:
    - example.com
  idporten:
    enabled: true
    integrationType: "api_klient"
    scopes:
      - "openid"
    authentication:
      enabled: true
  maskinporten:
    enabled: true
    authentication:
      enabled: true

When enabled, an Istio RequestAuthentication resource is generated to validate all incoming requests with a JWT. The correct issuer-uri, jwks-uri and client-id are retrieved from the kubernetes-Secret created by digdirator. I also needed to modify the generation of AuthorizationPolicy such that all paths requires the presence of a validated token with the correct issuer, except for /actuator* which by default is denied (unless authorizationSettings says otherwise).

The generated requestauthentication resource looks like this:

apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
  name: test-client-with-auth-jwt-authn
spec:
  jwtRules:
  - audiences:
    - <IDPORTEN CLIENT ID>
    forwardOriginalToken: true
    fromCookies:
    - BearerToken
    issuer: https://idporten.no
    jwksUri: https://idporten.no/jwks.json
  - audiences:
    - <MASKINPORTEN CLIENT ID>
    forwardOriginalToken: true
    issuer: https://maskinporten.no/
    jwksUri: https://maskinporten.no/jwk
  selector:
    matchLabels:
      app: test-client-with-auth
  selector:
    matchLabels:
      app: test-client-with-auth

and two authorizationpolicies are generated. One that default-denies /actuator* and one that enforces a JWT with the correct issuer:

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: test-client-jwt-auth
  namespace: lars
spec:
  rules:
  - from:
    - source:
        namespaces:
        - istio-gateways
    to:
    - operation:
        notPaths:
        - /actuator*
    when:
    - key: request.auth.claims[iss]
      values:
      - https://idporten.no
  - from:
    - source:
        namespaces:
        - istio-gateways
    to:
    - operation:
        notPaths:
        - /actuator*
    when:
    - key: request.auth.claims[iss]
      values:
      - https://maskinporten.no/
  selector:
    matchLabels:
      app: test-client

Additional parameters in Authentication

  • secretName: If an IDPorten or MaskinPorten client is registered outside of the application-manifest, the name of a kubernetes-Secret holding issuer-uri, jwks-uri and client-id can be provided.
  • forwardOriginalToken
  • tokenLocation: Is the JWT located as a BearerToken-cookie or as an Authorization-header?
  • outputClaimToHeaders: If you wish to copy claims from the JWT and add them as headers to the requests.

Summary by CodeRabbit

Based on the comprehensive changes, here are the high-level release notes:

  • New Features

    • Added JWT authentication configuration for applications
    • Introduced support for configuring authentication and authorization settings for ID Porten and Maskinporten providers
    • Enhanced security controls with Istio RequestAuthentication and AuthorizationPolicy resources
  • Authentication Enhancements

    • Added ability to validate incoming JSON Web Tokens (JWTs)
    • Support for forwarding original tokens
    • Configurable token extraction from headers or cookies
    • Option to map JWT claims to HTTP headers
  • Authorization Improvements

    • Granular path-based access control
    • Ability to specify ignore/allow paths
    • JWT claim-based authorization rules
  • Kubernetes Integration

    • New utility functions for retrieving client and secret data
    • Expanded CustomResourceDefinition for more flexible application configurations

These changes significantly improve the security and configuration flexibility for applications using ID Porten and Maskinporten authentication providers.

…specify how incoming JWT's should be authenticated. This definition will create a RequestAuthentication that validates incoming requests
…d workloads made from digdirator. Also setting up AuthorizationPolicy to enforce Bearer-token in requests
…and AuthPolicy from JWT-validation to a common AuthPolicy
Copy link
Contributor

coderabbitai bot commented Jan 21, 2025

Walkthrough

The pull request introduces comprehensive enhancements to authentication and authorization mechanisms within the Skiperator project. The changes primarily focus on adding JWT (JSON Web Token) validation capabilities for both IDPorten and Maskinporten identity providers. New structs, methods, and configurations have been added to support dynamic JWT authentication, allowing applications to specify token validation rules, claim forwarding, and path-based access controls. The modifications span across multiple packages, including API definitions, controllers, resource generators, and test configurations.

Changes

File Change Summary
api/v1alpha1/digdirator/idporten.go Added Authentication field of type *istiotypes.Authentication
api/v1alpha1/digdirator/maskinporten.go Added Authentication field of type *istiotypes.Authentication
api/v1alpha1/istiotypes/jwt_authentication.go New file defining Authentication and ClaimToHeader structs for JWT validation
internal/controllers/application.go Added method GetAuthConfigsForApplication and updated reconciliation process
pkg/reconciliation/reconciliation.go Added new types and methods for handling authentication configurations
pkg/resourcegenerator/istio/requestauthentication/request_authentication.go New file for generating Istio RequestAuthentication resources
pkg/util/helperfunctions.go Added utility functions for checking authentication settings and retrieving client information

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant Controller as ApplicationController
    participant AuthConfig as AuthConfigGenerator
    participant Istio as IstioResourceGenerator
    participant K8s as Kubernetes

    App->>Controller: Reconcile Request
    Controller->>AuthConfig: Get Authentication Configs
    AuthConfig-->>Controller: Return Auth Configurations
    Controller->>Istio: Generate RequestAuthentication
    Istio->>K8s: Create/Update Istio Resources
    K8s-->>Istio: Confirm Resource Creation
    Controller->>Istio: Generate AuthorizationPolicy
    Istio->>K8s: Create/Update Authorization Policies
Loading

Poem

🐰 Hop, hop, authentication's here to stay,
JWT tokens dancing their merry way!
From IDPorten to Maskinporten's gate,
Security's now our application's mate.
Claim by claim, we'll keep intruders at bay! 🔒

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

github-actions bot commented Jan 21, 2025

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails

Scanned Files

@larsore larsore changed the title Add JWT-validation Add JWT-validation for idporten/maskinporten Jan 29, 2025
@larsore larsore marked this pull request as ready for review January 29, 2025 08:53
@larsore larsore requested a review from a team as a code owner January 29, 2025 08:53
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🔭 Outside diff range comments (1)
tests/application/authorization-settings/patch-application-assert.yaml (1)

Line range hint 5-18: Ensure comprehensive path protection.

The policy explicitly allows /actuator/info and /actuator/shutdown. The inclusion of /actuator/shutdown endpoint could pose a security risk if not properly protected.

Consider removing the shutdown endpoint or adding additional authentication requirements:

  paths:
    - /actuator/info
-   - /actuator/shutdown
🧹 Nitpick comments (19)
pkg/resourcegenerator/istio/authorizationpolicy/authorization_policy.go (4)

35-35: Single default deny path

Defining defaultDenyPath := []string{"/actuator*"} is clear but be sure it covers all paths you want restricted. Some applications might host other sensitive endpoints.


45-66: Appending NotPaths to allowPaths

In this logic, NotPaths from each AuthConfig get appended to allowPaths. Consider whether these sets of paths should be distinct to avoid confusion in subsequent policy logic.


68-95: Conditional creation of ALLOW vs. DENY AuthorizationPolicy

The approach of creating an ALLOW policy if allowPaths is non-empty, and a DENY policy otherwise, is consistent with the intended “default deny” strategy. Just ensure it aligns with your application’s security stance—particularly for partial matches on "/actuator*".


138-171: Potential confusion in slice append for notPaths

Line 141 sets notPaths := allowPaths, then line 142 uses append(allowPaths, denyPaths...). This works but can be more intuitive by appending to notPaths directly (e.g., notPaths = append(notPaths, denyPaths...)).

internal/controllers/common/reconciler.go (1)

276-317: Typo in error message

In the MASKINPORTEN branch (line 303), the error text still references “IDPortenClient: '%s' not found.” This can cause confusion for troubleshooting. Consider correcting it to “MaskinPortenClient.”

- err := fmt.Errorf("IDPortenClient: '%s' not found", namespacedName.String())
+ err := fmt.Errorf("MaskinPortenClient: '%s' not found", namespacedName.String())
pkg/testutil/reconciliation.go (1)

30-30: Enhance test utility with authentication scenarios.

The test utility should demonstrate proper usage of the new authentication features instead of passing nil.

Consider adding test cases for authentication:

-	r := reconciliation.NewApplicationReconciliation(ctx, application, log.NewLogger(), false, nil, &identityConfigMap, nil)
+	authConfig := &istiotypes.Authentication{
+		Enabled: true,
+		TokenLocation: "header",
+		ForwardOriginalToken: true,
+	}
+	r := reconciliation.NewApplicationReconciliation(ctx, application, log.NewLogger(), false, nil, &identityConfigMap, authConfig)
pkg/reconciliation/reconciliation.go (1)

58-59: Use iota for IdentityProvider constants.

Consider using iota for the IdentityProvider constants to ensure uniqueness and make it easier to add new providers in the future.

-var (
-       MASKINPORTEN IdentityProvider = "MASKINPORTEN"
-       ID_PORTEN    IdentityProvider = "ID_PORTEN"
-)
+const (
+       MASKINPORTEN IdentityProvider = iota
+       ID_PORTEN
)
api/v1alpha1/digdirator/idporten.go (1)

82-83: Add kubebuilder validation markers for Authentication field.

Consider adding kubebuilder validation markers to enforce validation rules for the Authentication field, similar to other fields in the struct.

 // Authentication specifies how incoming JWT's should be validated.
+// +kubebuilder:validation:Optional
 Authentication *istiotypes.Authentication `json:"authentication,omitempty"`
tests/application/authorization-policy/application-assert.yaml (1)

4-4: LGTM! Naming changes improve clarity.

The renaming from 'authorization-policy-deny' to 'application-default-deny' better describes the policy's purpose, and the selector label update maintains consistency with the application renaming.

Note: Add a newline at the end of the file.

Also applies to: 18-18

tests/application/authorization-policy/chainsaw-test.yaml (2)

Line range hint 4-4: Update test name for consistency.

The test name should be updated to match the new policy name pattern.

-  name: authorization-policy
+  name: application-default-deny

Line range hint 20-22: Fix formatting.

Remove extra newlines at the end of the file, keeping just one.

tests/application/idporten/application-assert.yaml (1)

4-4: LGTM! Consistent renaming across deployment resources.

All references to 'idporten-test-client' have been properly updated to 'application', including deployment name, labels, container name, and volume/secret references.

Note: Add a newline at the end of the file.

Also applies to: 8-8, 12-12, 17-17, 21-21, 24-24

tests/application/maskinporten/application.yaml (1)

53-71: Review security of Secret resources.

The Secrets contain sensitive Maskinporten credentials. Consider:

  1. Using external secret management (e.g., HashiCorp Vault)
  2. Adding metadata labels for better secret management
  3. Implementing secret rotation mechanisms
metadata:
  name: "maskinporten-application-auth-6ef1ddb7"
+ labels:
+   app.kubernetes.io/component: auth
+   secret-rotation: enabled
🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 71-71: no new line character at the end of file

(new-line-at-end-of-file)

tests/application/maskinporten/application-maskinporten-auth-assert.yaml (1)

6-14: Enhance JWT validation configuration.

The JWT validation configuration looks good but consider adding:

  1. fromHeaders specification for explicit JWT location
  2. jwksUri timeout and caching settings
  3. Rate limiting for JWKS endpoint requests
  jwtRules:
    - audiences:
        - test-client-id-maskinporten
      forwardOriginalToken: true
      issuer: https://maskinporten.no/
      jwksUri: https://maskinporten.no/jwk
+     fromHeaders:
+       - name: Authorization
+         prefix: Bearer
+     jwtParams: ["access_token"]
      outputClaimToHeaders:
        - claim: aud
          header: Audience
tests/application/idporten/application-idporten-auth-assert.yaml (2)

26-38: Enhance JWT claim validation in authorization policy.

While the policy correctly validates the issuer claim, consider adding additional claim validations for enhanced security:

  • nbf (Not Before) claim
  • exp (Expiration Time) claim
  • sub (Subject) claim if applicable
  when:
    - key: request.auth.claims[iss]
      values:
        - https://idporten.no
+    - key: request.auth.claims[exp]
+      values:
+        - "*"
+    - key: request.auth.claims[nbf]
+      values:
+        - "*"

74-77: Remove redundant path exclusion.

The /actuator* exclusion is redundant and potentially confusing when combined with specific actuator path exclusions. Consider removing the generic exclusion since specific paths are already excluded.

  notPaths:
    - /actuator/health
    - /actuator/info
-    - /actuator*
tests/application/idporten/application.yaml (1)

30-35: Consider adding security-related claim mappings.

While mapping the "aud" claim is good, consider mapping additional security-relevant claims to headers for enhanced logging and debugging:

  • sub claim for subject identification
  • exp claim for token expiration tracking
  outputClaimToHeaders:
    - claim: "aud"
      header: "Audience"
+    - claim: "sub"
+      header: "X-User-ID"
+    - claim: "exp"
+      header: "X-Token-Expiry"
config/crd/skiperator.kartverket.no_applications.yaml (1)

556-619: Comprehensive JWT validation configuration for IDPorten.

The authentication block provides a well-structured configuration for JWT validation with appropriate security controls:

  • Required enabled flag for explicit opt-in
  • Flexible token location support (header/cookie)
  • Path-based authentication bypass with safety check (must start with '/')
  • Secure claim-to-header mapping

However, consider the following security recommendations:

  1. Add validation to ensure ignorePaths doesn't contain sensitive endpoints
  2. Consider adding a maximum length for header names in outputClaimToHeaders
tests/application/maskinporten/application-assert.yaml (1)

4-4: LGTM! Consistent resource renaming.

The renaming from maskinporten-test-client to application is applied consistently across all resources:

  • Deployment name
  • Selector labels
  • Container name
  • Volume mounts
  • Secret references

Add newline at end of file.

Add a newline character at the end of the file to comply with YAML best practices.

             secretName: "maskinporten-application-07ab8a18"
+

Also applies to: 8-8, 12-12, 17-17, 21-21, 24-24

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68d1320 and e1727c5.

📒 Files selected for processing (35)
  • api/v1alpha1/digdirator/idporten.go (2 hunks)
  • api/v1alpha1/digdirator/maskinporten.go (2 hunks)
  • api/v1alpha1/digdirator/zz_generated.deepcopy.go (3 hunks)
  • api/v1alpha1/istiotypes/jwt_authentication.go (1 hunks)
  • api/v1alpha1/istiotypes/zz_generated.deepcopy.go (1 hunks)
  • config/crd/skiperator.kartverket.no_applications.yaml (2 hunks)
  • internal/controllers/application.go (4 hunks)
  • internal/controllers/common/reconciler.go (2 hunks)
  • internal/controllers/common/util_test.go (0 hunks)
  • pkg/reconciliation/application.go (2 hunks)
  • pkg/reconciliation/reconciliation.go (3 hunks)
  • pkg/resourcegenerator/istio/authorizationpolicy/authorization_policy.go (2 hunks)
  • pkg/resourcegenerator/istio/requestauthentication/request_authentication.go (1 hunks)
  • pkg/resourceprocessor/diffs_test.go (1 hunks)
  • pkg/testutil/reconciliation.go (1 hunks)
  • pkg/util/helperfunctions.go (2 hunks)
  • tests/application/authorization-policy/application-assert.yaml (2 hunks)
  • tests/application/authorization-policy/application.yaml (1 hunks)
  • tests/application/authorization-policy/chainsaw-test.yaml (1 hunks)
  • tests/application/authorization-settings/multiple-application-assert.yaml (2 hunks)
  • tests/application/authorization-settings/multiple-application-errors.yaml (1 hunks)
  • tests/application/authorization-settings/patch-application-assert.yaml (2 hunks)
  • tests/application/authorization-settings/patch-application-errors.yaml (1 hunks)
  • tests/application/idporten/application-assert.yaml (1 hunks)
  • tests/application/idporten/application-idporten-assert.yaml (1 hunks)
  • tests/application/idporten/application-idporten-auth-assert.yaml (1 hunks)
  • tests/application/idporten/application.yaml (2 hunks)
  • tests/application/idporten/chainsaw-test.yaml (1 hunks)
  • tests/application/maskinporten/application-assert.yaml (1 hunks)
  • tests/application/maskinporten/application-maskinporten-assert.yaml (1 hunks)
  • tests/application/maskinporten/application-maskinporten-auth-assert.yaml (1 hunks)
  • tests/application/maskinporten/application.yaml (1 hunks)
  • tests/application/maskinporten/chainsaw-test.yaml (1 hunks)
  • tests/application/subresource-status/application-resource-apply-error-assert.yaml (1 hunks)
  • tests/application/subresource-status/application-synced-assert.yaml (1 hunks)
💤 Files with no reviewable changes (1)
  • internal/controllers/common/util_test.go
✅ Files skipped from review due to trivial changes (4)
  • tests/application/authorization-policy/application.yaml
  • tests/application/authorization-settings/patch-application-errors.yaml
  • tests/application/idporten/application-idporten-assert.yaml
  • tests/application/maskinporten/application-maskinporten-assert.yaml
🧰 Additional context used
🪛 YAMLlint (1.35.1)
tests/application/authorization-policy/application-assert.yaml

[error] 18-18: no new line character at the end of file

(new-line-at-end-of-file)

tests/application/maskinporten/application-maskinporten-auth-assert.yaml

[error] 82-82: no new line character at the end of file

(new-line-at-end-of-file)

tests/application/idporten/application.yaml

[error] 80-80: no new line character at the end of file

(new-line-at-end-of-file)

tests/application/maskinporten/application.yaml

[error] 71-71: no new line character at the end of file

(new-line-at-end-of-file)

tests/application/idporten/application-assert.yaml

[error] 24-24: no new line character at the end of file

(new-line-at-end-of-file)

tests/application/maskinporten/application-assert.yaml

[error] 24-24: no new line character at the end of file

(new-line-at-end-of-file)

🔇 Additional comments (33)
pkg/resourcegenerator/istio/authorizationpolicy/authorization_policy.go (5)

9-9: Imports appear correct for v1beta1 and types

The newly added imports for Istio’s v1beta1 package and Kubernetes types are necessary for your new code references. Everything looks consistent here.

Also applies to: 13-13


30-30: Early return when AllowAll is true

Skipping AuthorizationPolicy generation when AllowAll is set makes sense, but double-check if you want to handle special endpoints (e.g., /actuator) differently in such a scenario.


37-37: Initialization of allowPaths

Initializing and then assigning allowPaths from AllowList is a straightforward approach. Carefully verify that none of the paths in AllowList overlap undesirably with paths in defaultDenyPath.

Also applies to: 39-41


100-126: getGeneralAuthPolicy function clarity

The structure for constructing AuthorizationPolicy in getGeneralAuthPolicy is understandable. The explicit setting of Paths and NotPaths is good, but watch for potential collisions if these lists have overlapping patterns.


128-136: getGeneralFromRule restricts traffic source

This function restricts traffic to the istio-gateways namespace. Ensure this meets your requirements if there’s internal, namespace-to-namespace communication that also needs to be allowed.

internal/controllers/common/reconciler.go (2)

146-197: GetAuthConfigsForApplication

Logic for retrieving and assembling AuthConfig from secrets looks solid. Error handling for missing data keys is handled gracefully. Confirm that you have test coverage for cases where secrets are incomplete or incorrectly named.


199-274: getIdentityProviderInfoWithAuthenticationEnabled

Approach to require either “Enabled” providers or an explicit secretName is sensible. The error messages guide the user to correct configuration. This effectively enforces your JWT requirements.

pkg/reconciliation/application.go (1)

Line range hint 17-26: Extended NewApplicationReconciliation signature

The additional authConfigs parameter seamlessly integrates into your baseReconciliation flow. This supports the new JWT validation logic without breaking existing usage.

api/v1alpha1/digdirator/maskinporten.go (1)

21-23: LGTM! Well-structured authentication field addition.

The Authentication field is properly documented and correctly implemented as an optional pointer field.

api/v1alpha1/istiotypes/jwt_authentication.go (1)

16-17: Define explicit default for ForwardOriginalToken.

While the comment states the default is true, it's better to enforce this in the CRD validation.

Add the following kubebuilder marker to set the default value:

 // If set to true, the original token will be kept for the upstream request. Default is true.
+// +kubebuilder:default=true
 ForwardOriginalToken *bool `json:"forwardOriginalToken,omitempty"`
api/v1alpha1/digdirator/zz_generated.deepcopy.go (1)

44-48: LGTM! Proper deep copy implementation.

The generated code correctly handles deep copying of the Authentication field for both IDPorten and Maskinporten structs.

Also applies to: 74-78

pkg/reconciliation/reconciliation.go (1)

45-48: Consider adding validation for NotPaths.

The AuthConfig struct contains a pointer to a slice of strings for NotPaths. Consider adding validation to ensure these paths are well-formed and don't contain potentially dangerous patterns.

api/v1alpha1/istiotypes/zz_generated.deepcopy.go (1)

9-45: LGTM! The DeepCopyInto implementation looks correct.

The implementation properly handles all pointer fields and slices, ensuring deep copies are made correctly.

pkg/resourcegenerator/istio/requestauthentication/request_authentication.go (1)

74-76: ⚠️ Potential issue

Verify cookie security settings.

When using cookies for token storage, ensure secure cookie flags are set. Consider adding validation for secure cookie configuration.

pkg/resourceprocessor/diffs_test.go (1)

73-73: LGTM: Test updated correctly for new function signature.

The test has been properly updated to accommodate the new authentication configuration parameter.

internal/controllers/application.go (1)

Line range hint 198-218: LGTM: Proper integration of authentication components.

The changes correctly integrate the new authentication functionality:

  1. Added authConfigs parameter to NewApplicationReconciliation
  2. Added requestauthentication.Generate to the reconciliation functions
tests/application/authorization-settings/multiple-application-errors.yaml (1)

4-5: LGTM: Policy name change is more descriptive.

The renamed policy 'allow-all-default-deny' better describes its intended behavior.

tests/application/idporten/chainsaw-test.yaml (1)

17-18: LGTM! Good test coverage for JWT authentication.

The addition of the auth assertion ensures that the new JWT validation configuration is properly tested.

tests/application/maskinporten/chainsaw-test.yaml (1)

17-18: LGTM! Consistent test coverage across providers.

The auth assertion mirrors the idporten implementation, ensuring consistent JWT validation testing for both authentication providers.

tests/application/authorization-settings/multiple-application-assert.yaml (2)

Line range hint 23-36: Review the removal of DENY action.

The removal of the DENY action changes this to an ALLOW policy by default. Ensure this aligns with the security requirements, as it might be more permissive than intended.

Run this script to check for any similar patterns in other authorization policies:

✅ Verification successful

The removal of DENY action is consistent with the policy's purpose and security model

The codebase follows a clear pattern where default deny policies (with explicit DENY action) are paired with specific allow policies (without action) for necessary paths. The security model is maintained through this dual-policy approach, where all traffic is denied by default and only specific paths are allowed.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for AuthorizationPolicy resources without explicit DENY action
rg -l 'kind:\s*AuthorizationPolicy' | xargs yq -r 'select(.kind == "AuthorizationPolicy" and .spec.action == null) | .metadata.name'

Length of output: 171


Script:

#!/bin/bash
# Search for AuthorizationPolicy resources and their context
rg -A 10 -B 2 'kind:\s*AuthorizationPolicy' tests/

Length of output: 14485


Line range hint 4-18: Verify the security implications of the default deny policy.

The policy correctly denies access to /actuator* paths from istio-gateways namespace, but consider adding a catch-all rule to deny access from all other namespaces for enhanced security.

spec:
  action: DENY
  rules:
    - from:
        - source:
            namespaces:
              - istio-gateways
      to:
        - operation:
            paths:
              - /actuator*
+    - to:
+        - operation:
+            paths:
+              - /*
tests/application/authorization-settings/patch-application-assert.yaml (1)

Line range hint 24-38: LGTM: Default deny policy is properly configured.

The policy correctly maintains the DENY action and properly restricts access to /actuator* paths.

tests/application/subresource-status/application-synced-assert.yaml (1)

24-24: LGTM: Consistent naming update.

The status field correctly reflects the new naming convention for the AuthorizationPolicy resource.

tests/application/maskinporten/application.yaml (2)

24-29: Review the security implications of JWT claim mapping.

The configuration maps the aud claim to the Audience header. While this is valid, ensure that:

  1. The header name follows your organization's naming convention
  2. Downstream services are aware of this header mapping
  3. The claim mapping doesn't expose sensitive information

40-43: Verify completeness of allowed paths.

The allowList only includes health and info endpoints. Ensure this is sufficient for your application's needs and monitoring requirements.

Run this script to check for additional actuator endpoints that might need access:

✅ Verification successful

Actuator endpoints allowList is correctly configured

The current allowList configuration with /actuator/health and /actuator/info endpoints is appropriate and sufficient. This is consistent with the standard pattern used across the codebase and provides the necessary endpoints for basic monitoring and health checks while maintaining security by not exposing unnecessary endpoints.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for actuator endpoint usage in the codebase
rg -g '!*.yaml' -i 'actuator/[a-z]+' -A 3

Length of output: 1149


Script:

#!/bin/bash
# Search for actuator configuration in properties and yaml files
echo "=== Searching for actuator configuration ==="
rg -g '*.{properties,yaml,yml}' '(?i)management\.endpoints|spring\.actuator' -A 3

echo -e "\n=== Searching for custom actuator endpoint implementations ==="
rg -g '*.{java,kt}' '(?i)@endpoint|@webendpoint|@restcontrollerendpoint' -A 3

echo -e "\n=== Searching for other actuator-related code ==="
rg -g '*.{java,kt,xml,gradle}' '(?i)actuator(?!\/health|\/info)' -A 3

Length of output: 986


Script:

#!/bin/bash
echo "=== Searching for actuator configuration ==="
rg --debug -g '*.properties' -g '*.yaml' -g '*.yml' 'management.endpoint' || true
rg --debug -g '*.properties' -g '*.yaml' -g '*.yml' 'spring.actuator' || true

echo -e "\n=== Searching for custom actuator endpoint implementations ==="
rg --debug -g '*.java' -g '*.kt' '@Endpoint' || true
rg --debug -g '*.java' -g '*.kt' '@WebEndpoint' || true

echo -e "\n=== Searching for other actuator references ==="
rg -g '*.java' -g '*.kt' -g '*.xml' -g '*.gradle' 'actuator' || true

Length of output: 67022


Script:

#!/bin/bash
echo "=== Searching for actuator endpoints in application files ==="
fd -e yaml -e yml application tests | xargs grep -l "actuator" || true

echo -e "\n=== Searching for actuator endpoint patterns ==="
fd . -e yaml -e yml | xargs grep -l "/actuator/" || true

echo -e "\n=== Checking test files for actuator configuration ==="
fd . tests -e yaml | xargs grep -A 2 -B 2 "actuator" || true

Length of output: 9950

tests/application/maskinporten/application-maskinporten-auth-assert.yaml (1)

77-79: LGTM: Proper issuer validation.

The policy correctly validates the JWT issuer claim against the expected Maskinporten issuer URL.

tests/application/idporten/application-idporten-auth-assert.yaml (2)

43-61: LGTM! Well-structured allow policy for actuator endpoints.

The policy correctly allows access to essential health check endpoints while maintaining namespace-based restrictions.


10-11: Consider security implications of cookie-based tokens.

Using cookies for JWT storage (fromCookies: ["BearerToken"]) could expose the application to CSRF attacks. Consider implementing additional CSRF protection measures if not already in place.

Run the following script to check for CSRF protection:

tests/application/idporten/application.yaml (4)

Line range hint 4-14: LGTM! Basic configuration is correct.

The application configuration correctly enables idporten with the required scope.


46-49: LGTM! Well-structured allow list for actuator endpoints.

The authorization settings correctly specify the allowed health check endpoints.


78-80: Verify if duplicate credentials are intentional.

Both secrets contain identical credentials. Please verify if this is intentional or if the second secret should have different values for its specific use case.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 80-80: no new line character at the end of file

(new-line-at-end-of-file)


68-70: Verify the encoded credentials.

Please ensure that the base64-encoded values correspond to the correct:

  • Client ID
  • Issuer URL
  • JWKS URI
✅ Verification successful

Base64-encoded credentials verified successfully

All encoded values match their expected values exactly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Decode and verify the credentials

# Decode and verify the values
echo "Decoding IDPORTEN_CLIENT_ID..."
echo "dGVzdC1jbGllbnQtaWQtaWRwb3J0ZW4=" | base64 -d
echo -e "\nExpected: test-client-id-idporten"

echo -e "\nDecoding IDPORTEN_ISSUER..."
echo "aHR0cHM6Ly9pZHBvcnRlbi5ubw==" | base64 -d
echo -e "\nExpected: https://idporten.no"

echo -e "\nDecoding IDPORTEN_JWKS_URI..."
echo "aHR0cHM6Ly9pZHBvcnRlbi5uby9qd2tzLmpzb24=" | base64 -d
echo -e "\nExpected: https://idporten.no/jwks.json"

Length of output: 922


Script:

#!/bin/bash
python3 << 'EOF'
import base64

def decode_and_verify(encoded, expected):
    decoded = base64.b64decode(encoded).decode('utf-8')
    print(f"Decoded:   {decoded}")
    print(f"Expected:  {expected}")
    print(f"Matches:   {decoded == expected}\n")

print("Verifying IDPORTEN_CLIENT_ID:")
decode_and_verify("dGVzdC1jbGllbnQtaWQtaWRwb3J0ZW4=", "test-client-id-idporten")

print("Verifying IDPORTEN_ISSUER:")
decode_and_verify("aHR0cHM6Ly9pZHBvcnRlbi5ubw==", "https://idporten.no")

print("Verifying IDPORTEN_JWKS_URI:")
decode_and_verify("aHR0cHM6Ly9pZHBvcnRlbi5uby9qd2tzLmpzb24=", "https://idporten.no/jwks.json")
EOF

Length of output: 360

tests/application/subresource-status/application-resource-apply-error-assert.yaml (1)

22-22: LGTM! AuthorizationPolicy identifier update aligns with new naming convention.

The change from badport-deny to badport-default-deny correctly reflects the new authorization policy naming scheme introduced as part of the JWT validation feature.

api/v1alpha1/istiotypes/jwt_authentication.go Show resolved Hide resolved
api/v1alpha1/istiotypes/jwt_authentication.go Outdated Show resolved Hide resolved
api/v1alpha1/istiotypes/jwt_authentication.go Show resolved Hide resolved
Comment on lines +50 to +55
type ProviderURIs struct {
Provider IdentityProvider
IssuerURI string
JwksURI string
ClientID string
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding validation for URIs and ClientID.

The ProviderURIs struct contains critical security-related fields. Consider adding validation to ensure:

  • IssuerURI and JwksURI are valid HTTPS URLs
  • ClientID follows the expected format for the identity provider

Comment on lines +88 to +90
jwtRule.Issuer = providerURIs.IssuerURI
jwtRule.JwksUri = providerURIs.JwksURI
jwtRule.Audiences = []string{providerURIs.ClientID}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate JWT configuration values.

The JWT configuration values (Issuer, JWKS URI, and Audience) are critical security parameters. Consider adding validation to ensure:

  1. URIs use HTTPS
  2. JWKS URI is properly formatted
  3. Audience is not empty

pkg/util/helperfunctions.go Show resolved Hide resolved
Comment on lines +193 to +196
authConfigs, err := r.GetAuthConfigsForApplication(ctx, application)
if err != nil {
rLog.Error(err, "can't resolve auth config")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve error handling for auth config retrieval.

The error from GetAuthConfigsForApplication is only logged but not propagated, which could lead to silent failures in authentication setup.

 authConfigs, err := r.GetAuthConfigsForApplication(ctx, application)
 if err != nil {
-    rLog.Error(err, "can't resolve auth config")
+    rLog.Error(err, "failed to resolve auth config")
+    r.SetErrorState(ctx, application, err, "failed to resolve auth config", "AuthConfigError")
+    return common.RequeueWithError(err)
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 823 to 886
authentication:
description: Authentication specifies how incoming JWT's should
be validated.
properties:
enabled:
description: |-
Whether to enable JWT validation.
If enabled, incoming JWT's will be validated against the issuer specified in the app registration and the generated audience.
type: boolean
forwardOriginalToken:
description: If set to true, the original token will be kept
for the upstream request. Default is true.
type: boolean
ignorePaths:
description: |-
IgnorePaths specifies paths that do not require an authenticated JWT.

The specified paths must start with '/'.
items:
type: string
type: array
x-kubernetes-list-type: set
outputClaimToHeaders:
description: "This field specifies a list of operations to
copy the claim to HTTP headers on a successfully verified
token.\nThe header specified in each operation in the list
must be unique. Nested claims of type string/int/bool is
supported as well.\n```\n\n\toutputClaimToHeaders:\n\t-
header: x-my-company-jwt-group\n\t claim: my-group\n\t-
header: x-test-environment-flag\n\t claim: test-flag\n\t-
header: x-jwt-claim-group\n\t claim: nested.key.group\n\n```"
items:
properties:
claim:
description: The claim to be copied.
type: string
header:
description: The name of the HTTP-header for which the
specified claim will be copied to.
type: string
required:
- claim
- header
type: object
type: array
secretName:
description: |-
The name of the kubernetes Secret containing OAuth2-credentials.

If omitted, the associated client registration in the application manifest is used for JWT validation.
type: string
tokenLocation:
description: |-
Where to find the JWT in the incoming request

An enum value of `header` means that the JWT is present in the `Authorization` as a Bearer Token.
An enum value of `cookie` means that the JWT is present as a cookie called `BearerToken`.
enum:
- header
- cookie
type: string
required:
- enabled
type: object
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Verify duplicate authentication configuration.

The Maskinporten authentication block is identical to the IDPorten block. Consider extracting the common authentication schema to a shared definition to maintain DRY principles and ensure consistent validation rules.

+ # Add at the top level of the schema
+ definitions:
+   authentication:
+     description: Authentication specifies how incoming JWT's should be validated.
+     properties:
+       enabled:
+         description: Whether to enable JWT validation.
+         type: boolean
+       # ... rest of the authentication properties
+     required:
+       - enabled
+     type: object

  # In both idporten and maskinporten sections
- authentication:
-   description: Authentication specifies how incoming JWT's should be validated.
-   properties:
-     # ... duplicate properties
+ authentication:
+   $ref: "#/definitions/authentication"

Committable suggestion skipped: line range outside the PR's diff.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
pkg/util/helperfunctions.go (2)

81-81: ⚠️ Potential issue

Fix typo in function name.

The function name has a typo: GetMaskinPortenlient should be GetMaskinPortenClient.

-func GetMaskinPortenlient(k8sClient client.Client, ctx context.Context, namespacedName types.NamespacedName) (*nais_io_v1.MaskinportenClient, error) {
+func GetMaskinPortenClient(k8sClient client.Client, ctx context.Context, namespacedName types.NamespacedName) (*nais_io_v1.MaskinportenClient, error) {

92-99: ⚠️ Potential issue

Fix potential nil pointer dereference in GetSecretData.

The function doesn't check for errors from GetSecret before accessing secret.Data, which could lead to a nil pointer dereference.

 func GetSecretData(client client.Client, ctx context.Context, namespacedName types.NamespacedName, dataKeys []string) (map[string][]byte, error) {
     secret, err := GetSecret(client, ctx, namespacedName)
+    if err != nil {
+        return nil, fmt.Errorf("failed to get secret: %w", err)
+    }
     secretDataMap := map[string][]byte{}
     for _, key := range dataKeys {
         secretDataMap[key] = secret.Data[key]
     }
     return secretDataMap, err
 }
🧹 Nitpick comments (2)
pkg/util/helperfunctions.go (1)

196-198: Simplify boolean expression.

The comparison with true is redundant and can be simplified.

-func IsMaskinPortenAuthenticationEnabled(application *skiperatorv1alpha1.Application) bool {
-    return application.Spec.Maskinporten != nil && application.Spec.Maskinporten.Authentication != nil && application.Spec.Maskinporten.Authentication.Enabled == true
+func IsMaskinPortenAuthenticationEnabled(application *skiperatorv1alpha1.Application) bool {
+    return application.Spec.Maskinporten != nil && application.Spec.Maskinporten.Authentication != nil && application.Spec.Maskinporten.Authentication.Enabled
}
🧰 Tools
🪛 golangci-lint (1.62.2)

197-197: S1002: should omit comparison to bool constant, can be simplified to application.Spec.Maskinporten.Authentication.Enabled

(gosimple)

internal/controllers/application.go (1)

475-550: Consider refactoring to reduce code duplication.

The function has similar code blocks for both IDPorten and MaskinPorten providers. Consider extracting the common logic into a helper function.

+func getProviderInfo(application *skiperatorv1alpha1.Application, k8sClient client.Client, ctx context.Context, provider IdentityProvider) (*IdentityProviderInfo, error) {
+    var auth *istiotypes.Authentication
+    var enabled bool
+    var secretNamePtr *string
+    
+    switch provider {
+    case ID_PORTEN:
+        auth = application.Spec.IDPorten.Authentication
+        enabled = application.Spec.IDPorten.Enabled
+    case MASKINPORTEN:
+        auth = application.Spec.Maskinporten.Authentication
+        enabled = application.Spec.Maskinporten.Enabled
+    default:
+        return nil, fmt.Errorf("unsupported provider: %s", provider)
+    }
+    
+    if auth.SecretName != nil {
+        secretNamePtr = auth.SecretName
+    } else if enabled {
+        var err error
+        secretNamePtr, err = getSecretNameForIdentityProvider(k8sClient, ctx,
+            types.NamespacedName{
+                Namespace: application.Namespace,
+                Name:      application.Name,
+            },
+            provider,
+            application.UID)
+        if err != nil {
+            return nil, fmt.Errorf("failed to get secret name for %s: %w", provider, err)
+        }
+    } else {
+        return nil, fmt.Errorf("JWT authentication requires either %s to be enabled or a secretName to be provided", provider)
+    }
+    
+    return &IdentityProviderInfo{
+        Provider:   provider,
+        SecretName: *secretNamePtr,
+        NotPaths:   auth.IgnorePaths,
+    }, nil
+}

 func getIdentityProviderInfoWithAuthenticationEnabled(ctx context.Context, application *skiperatorv1alpha1.Application, k8sClient client.Client) ([]IdentityProviderInfo, error) {
     var providerInfo []IdentityProviderInfo
     
     if util.IsIDPortenAuthenticationEnabled(application) {
-        var secretName *string
-        var err error
-        if application.Spec.IDPorten.Authentication.SecretName != nil {
-            secretName = application.Spec.IDPorten.Authentication.SecretName
-        } else if application.Spec.IDPorten.Enabled {
-            secretName, err = getSecretNameForIdentityProvider(k8sClient, ctx,
-                types.NamespacedName{
-                    Namespace: application.Namespace,
-                    Name:      application.Name,
-                },
-                ID_PORTEN,
-                application.UID)
-        } else {
-            return nil, fmt.Errorf("JWT authentication requires either IDPorten to be enabled or a secretName to be provided")
-        }
-        if err != nil {
-            err := fmt.Errorf("failed to get secret name for IDPortenClient: %w", err)
-            return nil, err
-        }
-
-        var notPaths *[]string
-        if application.Spec.IDPorten.Authentication.IgnorePaths != nil {
-            notPaths = application.Spec.IDPorten.Authentication.IgnorePaths
-        } else {
-            notPaths = nil
-        }
-        providerInfo = append(providerInfo, IdentityProviderInfo{
-            Provider:   ID_PORTEN,
-            SecretName: *secretName,
-            NotPaths:   notPaths,
-        })
+        info, err := getProviderInfo(application, k8sClient, ctx, ID_PORTEN)
+        if err != nil {
+            return nil, err
+        }
+        providerInfo = append(providerInfo, *info)
     }
     
     if util.IsMaskinPortenAuthenticationEnabled(application) {
-        var secretName *string
-        var err error
-        if application.Spec.Maskinporten.Authentication.SecretName != nil {
-            secretName = application.Spec.Maskinporten.Authentication.SecretName
-        } else if application.Spec.Maskinporten.Enabled {
-            secretName, err = getSecretNameForIdentityProvider(k8sClient, ctx,
-                types.NamespacedName{
-                    Namespace: application.Namespace,
-                    Name:      application.Name,
-                },
-                MASKINPORTEN,
-                application.UID)
-        } else {
-            return nil, fmt.Errorf("JWT authentication requires either Maskinporten to be enabled or a secretName to be provided")
-        }
-        if err != nil {
-            err := fmt.Errorf("failed to get secret name for MaskinPortenClient: %w", err)
-            return nil, err
-        }
-
-        var notPaths *[]string
-        if application.Spec.Maskinporten.Authentication.IgnorePaths != nil {
-            notPaths = application.Spec.Maskinporten.Authentication.IgnorePaths
-        } else {
-            notPaths = nil
-        }
-        providerInfo = append(providerInfo, IdentityProviderInfo{
-            Provider:   MASKINPORTEN,
-            SecretName: *secretName,
-            NotPaths:   notPaths,
-        })
+        info, err := getProviderInfo(application, k8sClient, ctx, MASKINPORTEN)
+        if err != nil {
+            return nil, err
+        }
+        providerInfo = append(providerInfo, *info)
     }
     
     return providerInfo, nil
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39d87e8 and 8b72d12.

📒 Files selected for processing (3)
  • config/crd/skiperator.kartverket.no_applications.yaml (2 hunks)
  • internal/controllers/application.go (5 hunks)
  • pkg/util/helperfunctions.go (3 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
pkg/util/helperfunctions.go

197-197: S1002: should omit comparison to bool constant, can be simplified to application.Spec.Maskinporten.Authentication.Enabled

(gosimple)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Build and run tests
  • GitHub Check: Build and run tests
  • GitHub Check: Analyze (go)
🔇 Additional comments (4)
pkg/util/helperfunctions.go (2)

70-79: LGTM! Well-structured error handling.

The function follows Kubernetes client patterns with proper error handling and clear error messages.


192-194: LGTM! Safe nil checks.

The function properly checks for nil pointers before accessing nested fields, preventing potential panics.

internal/controllers/application.go (1)

422-473: LGTM! Well-structured authentication configuration retrieval.

The function properly handles:

  • Multiple identity providers
  • Secret data retrieval with proper error handling
  • Clear error messages
  • Edge case of no auth configs
config/crd/skiperator.kartverket.no_applications.yaml (1)

556-620: Extract common authentication schema.

The authentication block is identical between IDPorten and MaskinPorten. Consider extracting it to a shared definition to maintain DRY principles and ensure consistent validation rules.

+ # Add at the top level of the schema
+ definitions:
+   authentication:
+     description: Authentication specifies how incoming JWT's should be validated.
+     properties:
+       enabled:
+         description: Whether to enable JWT validation.
+         type: boolean
+       # ... rest of the authentication properties
+     required:
+       - enabled
+     type: object

  # In both idporten and maskinporten sections
- authentication:
-   description: Authentication specifies how incoming JWT's should be validated.
-   properties:
-     # ... duplicate properties
+ authentication:
+   $ref: "#/definitions/authentication"

Comment on lines +552 to +593
func getSecretNameForIdentityProvider(k8sClient client.Client, ctx context.Context, namespacedName types.NamespacedName, provider IdentityProvider, applicationUID types.UID) (*string, error) {
switch provider {
case ID_PORTEN:
idPortenClient, err := util.GetIdPortenClient(k8sClient, ctx, namespacedName)
if err != nil {
err := fmt.Errorf("failed to get IDPortenClient: %s", namespacedName.String())
return nil, err
}
if idPortenClient == nil {
err := fmt.Errorf("IDPortenClient: '%s' not found", namespacedName.String())
return nil, err
}
for _, ownerReference := range idPortenClient.OwnerReferences {
if ownerReference.UID == applicationUID {
return &idPortenClient.Spec.SecretName, nil
}
}
err = fmt.Errorf("no IDPortenClient with ownerRef to '%s' found", namespacedName.String())
return nil, err

case MASKINPORTEN:
maskinPortenClient, err := util.GetMaskinPortenlient(k8sClient, ctx, namespacedName)
if err != nil {
err := fmt.Errorf("failed to get MaskinPortenClient: %s", namespacedName.String())
return nil, err
}
if maskinPortenClient == nil {
err := fmt.Errorf("IDPortenClient: '%s' not found", namespacedName.String())
return nil, err
}
for _, ownerReference := range maskinPortenClient.OwnerReferences {
if ownerReference.UID == applicationUID {
return &maskinPortenClient.Spec.SecretName, nil
}
}
err = fmt.Errorf("no MaskinPortenClient with ownerRef to (%s) found", namespacedName.String())
return nil, err

default:
return nil, fmt.Errorf("provider: %s not supported", provider)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Refactor duplicate code and fix function name usage.

  1. The function has similar code blocks for both providers that can be extracted into a helper function.
  2. It uses the typo'd function name GetMaskinPortenlient.
+func getClientAndCheckOwner[T any](k8sClient client.Client, ctx context.Context, namespacedName types.NamespacedName, applicationUID types.UID, provider IdentityProvider, getClient func(client.Client, context.Context, types.NamespacedName) (*T, error)) (*T, error) {
+    client, err := getClient(k8sClient, ctx, namespacedName)
+    if err != nil {
+        return nil, fmt.Errorf("failed to get %s: %s", provider, namespacedName.String())
+    }
+    if client == nil {
+        return nil, fmt.Errorf("%s: '%s' not found", provider, namespacedName.String())
+    }
+    
+    // Use reflection to access OwnerReferences and Spec.SecretName
+    v := reflect.ValueOf(client).Elem()
+    ownerRefs := v.FieldByName("OwnerReferences").Interface().([]metav1.OwnerReference)
+    for _, ownerReference := range ownerRefs {
+        if ownerReference.UID == applicationUID {
+            secretName := v.FieldByName("Spec").FieldByName("SecretName").String()
+            return client, nil
+        }
+    }
+    return nil, fmt.Errorf("no %s with ownerRef to '%s' found", provider, namespacedName.String())
+}

 func getSecretNameForIdentityProvider(k8sClient client.Client, ctx context.Context, namespacedName types.NamespacedName, provider IdentityProvider, applicationUID types.UID) (*string, error) {
     switch provider {
     case ID_PORTEN:
-        idPortenClient, err := util.GetIdPortenClient(k8sClient, ctx, namespacedName)
-        if err != nil {
-            err := fmt.Errorf("failed to get IDPortenClient: %s", namespacedName.String())
-            return nil, err
-        }
-        if idPortenClient == nil {
-            err := fmt.Errorf("IDPortenClient: '%s' not found", namespacedName.String())
-            return nil, err
-        }
-        for _, ownerReference := range idPortenClient.OwnerReferences {
-            if ownerReference.UID == applicationUID {
-                return &idPortenClient.Spec.SecretName, nil
-            }
-        }
-        err = fmt.Errorf("no IDPortenClient with ownerRef to '%s' found", namespacedName.String())
-        return nil, err
+        client, err := getClientAndCheckOwner(k8sClient, ctx, namespacedName, applicationUID, provider, util.GetIdPortenClient)
+        if err != nil {
+            return nil, err
+        }
+        return &client.Spec.SecretName, nil

     case MASKINPORTEN:
-        maskinPortenClient, err := util.GetMaskinPortenlient(k8sClient, ctx, namespacedName)
-        if err != nil {
-            err := fmt.Errorf("failed to get MaskinPortenClient: %s", namespacedName.String())
-            return nil, err
-        }
-        if maskinPortenClient == nil {
-            err := fmt.Errorf("IDPortenClient: '%s' not found", namespacedName.String())
-            return nil, err
-        }
-        for _, ownerReference := range maskinPortenClient.OwnerReferences {
-            if ownerReference.UID == applicationUID {
-                return &maskinPortenClient.Spec.SecretName, nil
-            }
-        }
-        err = fmt.Errorf("no MaskinPortenClient with ownerRef to (%s) found", namespacedName.String())
-        return nil, err
+        client, err := getClientAndCheckOwner(k8sClient, ctx, namespacedName, applicationUID, provider, util.GetMaskinPortenClient)
+        if err != nil {
+            return nil, err
+        }
+        return &client.Spec.SecretName, nil

     default:
         return nil, fmt.Errorf("provider: %s not supported", provider)
     }
 }

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants