E12064: both constraint references extend the same base type

Category: E12xxx Security constraints · Reported in phase: PRE_IR_CHECKS · Classification: DUPLICATE_CONSTRAINT_ROLE · All error codes

What went wrong

A 'constrain by X and Y' clause has two references that extend the same base type. When two constraint references are provided, one must extend SecurityGate and the other must extend CORSPolicy. Having two SecurityGate implementations or two CORSPolicy implementations in the same constraint clause is not permitted.

Worked examples

The [EXAMPLE] section below shows a service constrained by two SecurityGates (the router expects at most one SecurityGate and one CORSPolicy), then by one gate, and by one gate and one CORS policy (good_cors.ek9).

Rationale

The constraint clause supports exactly two roles: authentication (SecurityGate) and cross-origin policy (CORSPolicy). Providing two implementations of the same role creates ambiguity about which one the router should invoke. Rather than defining precedence rules, EK9 requires one of each.

How to fix it

Ensure 'constrain by X and Y' has one SecurityGate and one CORSPolicy: 'constrain by JwtGate and ApiKeyGate', where both are security gates, fails; 'constrain by JwtGate and AllowedOrigins' (a gate and a CORS policy) is fine. If only authentication is needed (no CORS), use a single constraint: 'constrain by JwtGate'.

See also

  • Security posture required: E12050
  • Constraint type not resolved: E12060
  • Constraint type validation: E12062
  • Compilable examples: ek9 -q 1295 (Why does constrain by X and Y fail when both extend Sec...) ek9 -q 1294 (What types are valid in a constrain by clause?) ek9 -q 1292 (How does service security posture work with 'open' and ...) ek9 -q 1293 (What happens when a constrain by reference cannot be re...)

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E12064: both constraint references extend the same base type

#!ek9
defines module error.e12064.bad

  defines function

    GateAlpha is SecurityGate
      -> context as HTTPContext
      <- rtn as HTTPContext: context

    GateBeta is SecurityGate
      -> context as HTTPContext
      <- rtn as HTTPContext: context

  defines service

    DuplicateRoleService for :/api constrain by GateAlpha and GateBeta
      health() as GET for :/status
        <- response as HTTPResponse: () with trait of HTTPResponse
          override status() as pure
            <- rtn as Integer: 200
          override content()
            <- rtn as Bytes: Bytes().withUtf8("ok")
          override contentType() as pure
            <- rtn as String: "text/plain"
          default operator ?

Fixed: One SecurityGate (and at most one CORSPolicy) per service

#!ek9
defines module error.e12064.good

  defines function

    ApiGate is SecurityGate
      -> context as HTTPContext
      <- rtn as HTTPContext: context

  defines service

    GatedService for :/api constrain by ApiGate

      health() as GET for :/status
        <- response as HTTPResponse: () with trait of HTTPResponse
          override status() as pure
            <- rtn as Integer: 200
          override content()
            <- rtn as Bytes: Bytes().withUtf8("ok")
          override contentType() as pure
            <- rtn as String: "text/plain"
          default operator ?

Fixed: A SecurityGate and a CORSPolicy together are two roles, not a duplicate

#!ek9
defines module error.e12064.good.cors

  defines function

    ApiGate is SecurityGate
      -> context as HTTPContext
      <- rtn as HTTPContext: context

    AllowedOrigins is CORSPolicy
      -> origin as String
      <- rtn as Boolean: origin == "https://ek9.io"

  defines service

    GatedService for :/api constrain by ApiGate and AllowedOrigins

      health() as GET for :/status
        <- response as HTTPResponse: () with trait of HTTPResponse
          override status() as pure
            <- rtn as Integer: 200
          override content()
            <- rtn as Bytes: Bytes().withUtf8("ok")
          override contentType() as pure
            <- rtn as String: "text/plain"
          default operator ?