# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

rules:
  # Check potentially RPC endpoints with missing authentication/authorization.
  - id: "rpc-potentially-unauthenticated"
    patterns:
      - pattern: |
          if done, err := $A.$B.forward($METHOD, ...); done {
            return err
          }
      # Pattern used by typical endpoints that take an auth token or workload
      # identity. Some of these endpoints have no context for Authenticate
      - pattern-not-inside: |
          authErr := $A.$B.Authenticate(...)
          ...
          if done, err := $A.$B.forward($METHOD, ...); done {
            return err
          }
          ...
          ... := $A.$B.ResolveACL(...)
          ...

      # Pattern used by endpoints that are used only for server-to-server. The
      # authentication and authorization check must be done together before
      # forwarding to prevent the risk of confused deputy when RPCs are
      # forwarded.
      - pattern-not-inside: |

          aclObj, err := $A.srv.AuthenticateServerOnly($A.ctx, args)
          ...
          if err != nil || !aclObj.AllowServerOp() {
            return structs.ErrPermissionDenied
          }

          if done, err := $A.srv.forward($METHOD, ...); done {
            return err
          }
          ...

      # Pattern used by endpoints that are used only for client-to-server.
      # Authorization can be done after forwarding, but must check the
      # AllowClientOp policy; the AllowClientOp condition is left open so that
      # additional ACL checks can be made (ex. to scope to a given node/pool).
      - pattern-not-inside: |
          aclObj, err := $A.$B.AuthenticateClientOnly($A.ctx, args)
          ...
          if done, err := $A.$B.forward($METHOD, ...); done {
            return err
          }
          ...
          if !aclObj.AllowClientOp() {
            return structs.ErrPermissionDenied
          }
          ...

      # Pattern used by endpoints that are used only for client-to-server.
      # Authorization can be done after forwarding, but must check the
      # AllowClientOp policy. This should not be added to any new endpoints.
      - pattern-not-inside: |
          aclObj, err := $A.$B.AuthenticateClientOnlyLegacy($A.ctx, args)
          ...
          if done, err := $A.$B.forward($METHOD, ...); done {
            return err
          }
          ...
          if !aclObj.AllowClientOp() {
            return structs.ErrPermissionDenied
          }
          ...

      # Pattern used by ACL endpoints that need to interact with the token
      # directly.
      - pattern-not-inside: |
          authErr := $A.$B.Authenticate($A.ctx, args)
          ...
          if done, err := $A.$B.forward($METHOD, ...); done {
            return err
          }
          ...
          ... := args.GetIdentity().GetACLToken()
          ...

      - metavariable-pattern:
          metavariable: $METHOD
          patterns:
            # Endpoints that are expected not to have authentication.
            - pattern-not: '"ACL.Bootstrap"'
            - pattern-not: '"ACL.GetClaimPolicies"'
            - pattern-not: '"ACL.ResolveToken"'
            - pattern-not: '"ACL.UpsertOneTimeToken"'
            - pattern-not: '"ACL.ExchangeOneTimeToken"'
            - pattern-not: '"ACL.WhoAmI"'
            - pattern-not: 'structs.ACLListAuthMethodsRPCMethod'
            - pattern-not: 'structs.ACLOIDCAuthURLRPCMethod'
            - pattern-not: 'structs.ACLOIDCCompleteAuthRPCMethod'
            - pattern-not: 'structs.ACLLoginRPCMethod'
            - pattern-not: '"Status.Leader"'
            - pattern-not: '"Status.Peers"'
            - pattern-not: '"Status.Version"'
            - pattern-not: '"Keyring.ListPublic"'
            - pattern-not: '"Keyring.GetConfig"'
    message: "RPC method $METHOD appears to be unauthenticated"
    languages:
      - "go"
    severity: "WARNING"
    paths:
      include:
        - "nomad/*_endpoint.go"


  # ACL objects should never be nil-checked in RPC handlers before checking
  # authorization, as nil ACLs are always programmer errors.
  - id: "rpc-authz-bypass"
    patterns:
      # Pattern that may accidentally bypass authorization checks.
      - pattern: |
          aclObj == nil

    message: "RPC method ACL check $ACL_CHECK appears to bypass authorization by first checking for nil ACLs"
    languages:
      - "go"
    severity: "WARNING"
    paths:
      include:
        - "nomad/*_endpoint.go"
