PageSourceSearch

https://www.openpolicyagent.org/assets/js/f655f431.846a2754.js

js openpolicyagent.org collected 2026-09-24 08:27:45 UTC 103,698 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkopa_website=self.webpackChunkopa_website||[]).push([[3467],{11671:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>r,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>t,toc:()=>d});const t=JSON.parse('{"id":"management-bundles/index","title":"Bundles","description":"Many use cases require that OPA reload policy and related data while serving","source":"@site/docs/management-bundles/index.md","sourceDirName":"management-bundles","slug":"/management-bundles/","permalink":"/docs/management-bundles/","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{"title":"Bundles"},"sidebar":"docsSidebar","previous":{"title":"Overview","permalink":"/docs/management-introduction/"},"next":{"title":"Decision Logs","permalink":"/docs/management-decision-logs"}}');var s=i(74848),a=i(28453);const l={title:"Bundles"},o=void 0,r={},d=[{value:"Bundle build",id:"bundle-build",level:2},{value:"Bundle Service API",id:"bundle-service-api",level:2},{value:"Caching",id:"caching",level:3},{value:"HTTP Long Polling",id:"http-long-polling",level:3},{value:"Bundle File Format",id:"bundle-file-format",level:2},{value:"Manifest JSON Schema",id:"manifest-json-schema",level:3},{value:"Multiple Sources of Policy and Data",id:"multiple-sources-of-policy-and-data",level:2},{value:"Debugging Your Bundles",id:"debugging-your-bundles",level:2},{value:"Signing",id:"signing",level:2},{value:"Signature Format",id:"signature-format",level:3},{value:"Signature Verification",id:"signature-verification",level:4},{value:"Signature Plugin",id:"signature-plugin",level:4},{value:"Delta Bundles",id:"delta-bundles",level:2},{value:"Delta Bundle File Format",id:"delta-bundle-file-format",level:3},{value:"Delta Bundle Patch Operations",id:"delta-bundle-patch-operations",level:4},{value:"Current Limitations",id:"current-limitations",level:4},{value:"Delta Bundle FAQ",id:"delta-bundle-faq",level:4},{value:"Implementations",id:"implementations",level:2},{value:"Amazon S3",id:"amazon-s3",level:3},{value:"OPA Bundle Support",id:"opa-bundle-support",level:4},{value:"Setup Instructions",id:"setup-instructions",level:4},{value:"Authentication",id:"authentication",level:4},{value:"Environment Credentials",id:"environment-credentials",level:5},{value:"Metadata Credentials",id:"metadata-credentials",level:5},{value:"Web Identity Credentials",id:"web-identity-credentials",level:5},{value:"Testing Authentication",id:"testing-authentication",level:5},{value:"Upload Bundle",id:"upload-bundle",level:4},{value:"Example OPA Configuration",id:"example-opa-configuration",level:4},{value:"Environment Credentials",id:"environment-credentials-1",level:5},{value:"Metadata Credentials",id:"metadata-credentials-1",level:5},{value:"Assume Role Credentials",id:"assume-role-credentials",level:5},{value:"Web Identity Credentials",id:"web-identity-credentials-1",level:5},{value:"Credential Provider Chaining",id:"credential-provider-chaining",level:5},{value:"Google Cloud Storage",id:"google-cloud-storage",level:3},{value:"OPA Bundle Support",id:"opa-bundle-support-1",level:4},{value:"Setup Instructions",id:"setup-instructions-1",level:4},{value:"Authentication",id:"authentication-1",level:4},{value:"GCP Metadata Token Authentication",id:"gcp-metadata-token-authentication",level:5},{value:"JWT Bearer Grant Type",id:"jwt-bearer-grant-type",level:5},{value:"Testing Authentication",id:"testing-authentication-1",level:5},{value:"Upload Bundle",id:"upload-bundle-1",level:4},{value:"Example OPA Configuration",id:"example-opa-configuration-1",level:4},{value:"GCP Metadata Token Authentication",id:"gcp-metadata-token-authentication-1",level:5},{value:"Google Cloud Storage Bundle and JWT Bearer Authentication",id:"google-cloud-storage-bundle-and-jwt-bearer-authentication",level:5},{value:"Azure Blob Storage",id:"azure-blob-storage",level:3},{value:"OPA Bundle Support",id:"opa-bundle-support-2",level:4},{value:"Setup Instructions",id:"setup-instructions-2",level:4},{value:"Authentication",id:"authentication-2",level:4},{value:"Testing Authentication",id:"testing-authentication-2",level:5},{value:"Upload Bundle",id:"upload-bundle-2",level:4},{value:"Example OPA Configuration",id:"example-opa-configuration-2",level:4},{value:"Azure Blob Storage Bundle and Client Credentials Authentication",id:"azure-blob-storage-bundle-and-client-credentials-authentication",level:5},{value:"Azure Blob Storage Bundle and Client Credentials JWT Authentication",id:"azure-blob-storage-bundle-and-client-credentials-jwt-authentication",level:5},{value:"Nginx",id:"nginx",level:3},{value:"Upload Bundle",id:"upload-bundle-3",level:4},{value:"Example OPA Configuration",id:"example-opa-configuration-3",level:4},{value:"OCI Registry",id:"oci-registry",level:3},{value:"Building and Publishing Policy Containers",id:"building-and-publishing-policy-containers",level:4},{value:"Using OPA and ORAS CLIs",id:"using-opa-and-oras-clis",level:5},{value:"Maintaining a policy-as-code repository",id:"maintaining-a-policy-as-code-repository",level:4},{value:"Example",id:"example",level:4},{value:"Starting from scratch",id:"starting-from-scratch",level:5},{value:"Building your policy",id:"building-your-policy",level:6},{value:"Pushing the container to a remote registry",id:"pushing-the-container-to-a-remote-registry",level:6},{value:"Spin up the policy with OPA CLI",id:"spin-up-the-policy-with-opa-cli",level:6},{value:"Ecosystem Projects",id:"ecosystem-projects",level:2}];function c(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",h4:"h4",h
15:"h5",h6:"h6",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.R)(),...e.components},{EcosystemEmbed:t}=n;return t||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("EcosystemEmbed",!0),(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.p,{children:"Many use cases require that OPA reload policy and related data while serving\nrequests. OPA is commonly deployed as a supporting role to applications and\nother callers, so it's often desirable to be able to update policies quicker\nthan would be possible with a full redeployment.\nFrequent data updates are another driver for this functionality."}),"\n",(0,s.jsxs)(n.p,{children:["Updated policies and data are loaded on the fly without requiring a restart of\nOPA. Once the policies and data have been loaded, they are enforced immediately.\nPolicies and data loaded from bundles are accessible via the standard OPA\n",(0,s.jsx)(n.a,{href:"./rest-api",children:"REST API"})," to callers."]}),"\n",(0,s.jsx)(n.p,{children:"Bundles provide an alternative to pushing policies into OPA via the REST APIs.\nBy configuring OPA to download bundles from a remote HTTP server, you can\nensure that OPA has an up-to-date copy of policies and data required for\nenforcement at all times in an eventually consistent manner."}),"\n",(0,s.jsx)(n.p,{children:"By default, the OPA REST APIs will prevent you from modifying policy and data\nloaded via bundles. If you need to load policy and data from multiple sources,\nsee the section below."}),"\n",(0,s.jsxs)(n.p,{children:["See the ",(0,s.jsx)(n.a,{href:"./configuration",children:"Configuration Reference"})," for configuration details."]}),"\n",(0,s.jsx)(n.h2,{id:"bundle-build",children:"Bundle build"}),"\n",(0,s.jsxs)(n.p,{children:["The CLI command ",(0,s.jsx)(n.a,{href:"./cli/#build",children:(0,s.jsx)(n.code,{children:"opa build"})})," gives you the capability to build your own bundles."]}),"\n",(0,s.jsxs)(n.p,{children:["Here is a basic example on how to build a bundle from a folder called ",(0,s.jsx)(n.code,{children:"foo"}),". The bundle will be named by default ",(0,s.jsx)(n.code,{children:"bundle.tar.gz"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",metastring:'title="example.rego"',children:'package authz\n\nallow if {\n    input.path == ["users"]\n    input.method == "POST"\n}\n\nallow if {\n    input.path == ["users", input.user_id]\n    input.method == "GET"\n}\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-console",children:"$ ls foo/\nexample.rego\n\n$ opa build -b foo/\n"})}),"\n",(0,s.jsxs)(n.p,{children:["More, you can optimize the bundle by specifying the ",(0,s.jsx)(n.code,{children:"--optimize"})," or ",(0,s.jsx)(n.code,{children:"-O"})," flag. This does require defining an entrypoint.\nYou can provide an entrypoint using the CLI flag ",(0,s.jsx)(n.code,{children:"--entrypoint"})," or as\na ",(0,s.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/policy-language#metadata",children:"Metadata annotation"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-console",children:"opa build -b foo/ --optimize=1 --entrypoint authz/allow\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Finally, you can also sign your bundle with ",(0,s.jsx)(n.code,{children:"opa build"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-console",children:"opa build --verification-key /path/to/public_key.pem --signing-key /path/to/private_key.pem --bundle foo/\n"})}),"\n",(0,s.jsxs)(n.p,{children:["For more information, see the ",(0,s.jsxs)(n.a,{href:"./cli/#build",children:[(0,s.jsx)(n.code,{children:"opa build"})," command documentation."]})]}),"\n",(0,s.jsx)(n.h2,{id:"bundle-service-api",children:"Bundle Service API"}),"\n",(0,s.jsxs)(n.p,{children:["OPA expects the service to expose an API endpoint that serves bundles. The\nbundle API should allow clients to download bundles at an arbitrary URL. In\ncombination with a service's ",(0,s.jsx)(n.code,{children:"url"})," path."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-http",children:"GET /<service path>/<resource> HTTP/1.1\n"})}),"\n",(0,s.jsx)(n.p,{children:"If the bundle exists, the server should respond with an HTTP 200 OK status\nfollowed by a gzipped tarball in the message body."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-http",children:"HTTP/1.1 200 OK\nContent-Type: application/gzip\n"})}),"\n",(0,s.jsx)(n.p,{children:"Enable bundle downloading via configuration. For example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:'services:\n- name: acmecorp\n  url: https://example.com/service/v1\n  credentials:\n    bearer:\n      token: "bGFza2RqZmxha3NkamZsa2Fqc2Rsa2ZqYWtsc2RqZmtramRmYWxkc2tm"\n\nbundles:\n  authz:\n    service: acmecorp\n    resource: somedir/bundle.tar.gz\n    persist: true\n    polling:\n      min_delay_seconds: 10\n      max_delay_seconds: 20\n    signing:\n      keyid: my_global_key\n      scope: read\n\nkeys:\n  my_global_key:\n    algorithm: RS256\n    
1key: |\n      -----BEGIN PUBLIC KEY-----\n      MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n      -----END PUBLIC KEY-----\n'})}),"\n",(0,s.jsxs)(n.p,{children:["See ",(0,s.jsx)(n.a,{href:"./configuration/#keys",children:"Configuration - Keys"})," for details on key configuration."]}),"\n",(0,s.jsxs)(n.p,{children:["Using this configuration, OPA will fetch bundles from\n",(0,s.jsx)(n.code,{children:"https://example.com/service/v1/somedir/bundle.tar.gz"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"The URL is constructed as follows:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"https://example.com/service/v1/somedir/bundle.tar.gz\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^\nservices[0].url                resource\n"})}),"\n",(0,s.jsxs)(n.p,{children:["If the ",(0,s.jsx)(n.code,{children:"bundles[_].resource"})," field is not defined, the value defaults to\n",(0,s.jsx)(n.code,{children:"bundles/<name>"})," where the ",(0,s.jsx)(n.code,{children:"name"})," is the key value in the configuration. For the\nexample above this is ",(0,s.jsx)(n.code,{children:"authz"})," and would default to ",(0,s.jsx)(n.code,{children:"bundles/authz"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["Bundle names can have any valid YAML characters in them, including ",(0,s.jsx)(n.code,{children:"/"}),". This can\nbe useful when relying on default ",(0,s.jsx)(n.code,{children:"resource"})," behavior with a name like\n",(0,s.jsx)(n.code,{children:"authz/bundle.tar.gz"})," which results in a ",(0,s.jsx)(n.code,{children:"resource"})," of\n",(0,s.jsx)(n.code,{children:"bundles/authz/bundle.tar.gz"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["OPA can optionally persist activated bundles to disk for recovery purposes. To enable\npersistence, set the ",(0,s.jsx)(n.code,{children:"bundles[_].persist"})," field to ",(0,s.jsx)(n.code,{children:"true"}),". When bundle\npersistence is enabled, OPA will attempt to read the bundle from disk on startup. This\nallows OPA to start with the most recently activated bundle in case OPA cannot communicate\nwith the bundle server. OPA will try to load and activate persisted bundles on a best-effort basis. Any errors\nencountered during the process will be surfaced in the bundle's status update. When communication between OPA and\nthe bundle server is restored, the latest bundle is downloaded, activated, and persisted."]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["By default, bundles are persisted under the current working directory of the OPA process (e.g., ",(0,s.jsx)(n.code,{children:"./.opa
1/bundles/<bundle-name>/bundle.tar.gz"}),")."]})}),"\n",(0,s.jsxs)(n.p,{children:["The optional ",(0,s.jsx)(n.code,{children:"bundles[_].signing"})," field can be used to specify the ",(0,s.jsx)(n.code,{children:"keyid"})," and ",(0,s.jsx)(n.code,{children:"scope"})," that should be used\nfor verifying the signature of the bundle. See ",(0,s.jsx)(n.a,{href:"#signing",children:"this"})," section for details."]}),"\n",(0,s.jsx)(n.p,{children:"See the following section for details on the bundle file format."}),"\n",(0,s.jsx)(n.h3,{id:"caching",children:"Caching"}),"\n",(0,s.jsxs)(n.p,{children:["Services implementing the Bundle Service API should set the HTTP ",(0,s.jsx)(n.code,{children:"Etag"})," header\nin bundle responses to identify the revision of the bundle. OPA will include the\n",(0,s.jsx)(n.code,{children:"Etag"})," value in the ",(0,s.jsx)(n.code,{children:"If-None-Match"})," header of bundle requests. Services can\ncheck the ",(0,s.jsx)(n.code,{children:"If-None-Match"})," header and reply with HTTP ",(0,s.jsx)(n.code,{children:"304 Not Modified"})," if the\nbundle has not changed since the last update."]}),"\n",(0,s.jsx)(n.h3,{id:"http-long-polling",children:"HTTP Long Polling"}
1),"\n",(0,s.jsxs)(n.p,{children:["With the periodic bundle downloading (i.e. ",(0,s.jsx)(n.code,{children:"short polling"}),") technique, OPA sends regular requests to the remote HTTP\nserver to pull any available bundle. If there is no new bundle, the server responds with a ",(0,s.jsx)(n.code,{children:"304 Not Modified"})," response.\nThe polling frequency depends on the latency that the client can tolerate in\nretrieving updated information from the server. A drawback of this\nmethod is that if the acceptable latency is low, then the polling frequency could add unnecessary\nburden on the server and/or network."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.a,{href:"https://datatracker.ietf.org/doc/html/rfc6202#section-2",children:"HTTP Long Polling"})," helps to minimize server/network resource\nusage and also reduces the delay in delivery of updates to the client. When OPA sends a long poll request to the server,\nit defers its response until an update is available or timeout has occurred. In case of a timeout, the server responds\nwith a ",(0,s.jsx)(n.code,{children:"304 Not Modified"})," response."]}),"\n",(0,s.jsxs)(n.p,{children:["The below configuration shows how to enable bundle downloading via ",(0,s.jsx)(n.code,{children:"long polling"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:'services:\n- name: acmecorp\n  url: https://example.com/service/v1\n  credentials:\n    bearer:\n      token: "bGFza2RqZmxha3NkamZsa2Fqc2Rsa2ZqYWtsc2RqZmtramRmYWxkc2tm"\n\nbundles:\n  authz:\n    service: acmecorp\n    resource: somedir/bundle.tar.gz\n    persist: true\n    polling:\n      long_polling_timeout_seconds: 10\n    signing:\n      keyid: my_global_key\n      scope: read\n'})}),"\n",(0,s.jsxs)(n.p,{children:["With the above configuration, OPA sends a long poll request to the server with a timeout set to ",(0,s.jsx)(n.code,{children:"10"})," seconds. If the server\nsupports ",(0,s.jsx)(n.code,{children:"long polling"}),", OPA expects the server to set the ",(0,s.jsx)(n.code,{children:"Content-Type"})," header to ",(0,s.jsx)(n.code,{children:"application/vnd.openpolicyagent.bundles"}),".\nIf the server does not support ",(0,s.jsx)(n.code,{children:"long polling"}),", OPA will fallback to the regular periodic polling."]}),"\n",(0,s.jsx)(n.h2,{id:"bundle-file-format",children:"Bundle File Format"}),"\n",(0,s.jsxs)(n.p,{children:["Bundle files are gzipped tarballs (",(0,s.jsx)(n.code,{children:".tar.gz"}),") that contain policies and/or\ndata."]}),"\n",(0,s.jsxs)(n.p,{children:["Policy files are Rego source files with the ",(0,s.jsx)(n.code,{children:".rego"})," extension and will be\navailable within Rego modules based on their package's path,\ne.g. ",(0,s.jsx)(n.code,{children:"package example.authz"})," is available at ",(0,s.jsx)(n.code,{children:"data.example.authz"})," in the\n",(0,s.jsxs)(n.a,{href:"./philosophy/#the-opa-document-model",children:[(0,s.jsx)(n.code,{children:"data"})," Document"]}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["The data files within the bundle can be organized hierarchically into\ndirectories inside the tarball. The hierarchical organization indicates to OPA\nwhere to load the data files into the ",(0,s.jsx)(n.code,{children:"data"})," Document - similar to how Rego\nfiles can control their location using the package path. This functionality\ncan be useful when:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Deploying larger bundles with policy for different callers and use cases."}),"\n",(0,s.jsx)(n.li,{children:"Creating bundles where different teams manage different parts of the bundle.\nFor example, some shared policy is to be loaded alongside some application\nspecific policy and data."}),"\n",(0,s.jsxs)(n.li,{children:["When some parts of the data are to be updated more frequently than others,\ne.g. using ",(0,s.jsx)(n.a,{href:"#delta-bundles",children:"Delta Bundles"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["You can list the content of a bundle with ",(0,s.jsx)(n.code,{children:"tar"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"$ tar tzf bundle.tar.gz\n.manifest\nroles\nroles/bindings\nroles/bindings/data.json\nroles/permissions\nroles/permissions/data.json\nhttp\nhttp/example\nhttp/example/authz\nhttp/example/authz/authz.rego\n"})}),"\n",(0,s.jsxs)(n.p,{children:["In this example, the bundle contains one policy file (",(0,s.jsx)(n.code,{children:"authz.rego"}),") and two\ndata files (",(0,s.jsx)(n.code,{children:"roles/bindings/data.json"})," and ",(0,s.jsx)(n.code,{children:"roles/permissions/data.json"}),").\nA data file in the root of the bundle will be loaded into the ",(0,s.jsx)(n.code,{children:"data"})," Document at\nthe root. For example, the ",(0,s.jsx)(n.code,{children:"foo"})," key is inserted at the\nroot of the ",(0,s.jsx)(n.code,{children:"data"})," document:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sh",children:'$ tree bundle\nbundle\n\u2514\u2500\u2500 data.json\n\n1 directory, 1 file\n$ cat bundle/data.json\n{ "foo": true }\n$ opa eval -b bundle/ data.foo --format=raw\ntrue\n'})}
1),"\n",(0,s.jsxs)(n.p,{children:["The bundle may also contain an optional Wasm binary file (",(0,s.jsx)(n.code,{children:"policy.wasm"}),").\nOPA stores the WebAssembly compiled version of all the Rego policy files within\nthe bundle in ",(0,s.jsx)(n.code,{children:"policy.wasm"})," when building with ",(0,s.jsx)(n.code,{children:"-t wasm"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sh",children:'$ cat -p bundle/http/example/authz/authz.rego\npackage http.example.authz\n\nallow := true\n$ opa build -t wasm -e http/example/authz/allow bundle\n$ tar tzf bundle.tar.gz\n/data.json\n/bundle/http/example/authz/authz.rego\n/policy.wasm\n/.manifest\n$ opa eval -b bundle.tar.gz data --format=raw\n{"http":{"example":{"authz":{"allow":true}}}}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Bundle files may contain an optional ",(0,s.jsx)(n.code,{children:".manifest"})," file that stores bundle\nmetadata. The file should contain a JSON serialized object, with the following\nfields:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"revision"})," - If the bundle service is capable of serving different revisions of the same\nbundle, the service should include a top-level ",(0,s.jsx)(n.code,{children:"revision"})," field containing a\n",(0,s.jsx)(n.code,{children:"string"})," value that identifies the bundle revision."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"rego_version"})," - An optional field that specifies the rego-version of the Rego source files\nin the bundle. The value of this field is an ",(0,s.jsx)(n.code,{children:"integer"}),"; where ",(0,s.jsx)(n.code,{children:"0"})," corresponds to v0 Rego (",(0,s.jsx)(n.a,{href:"./v0-compatibility/",children:"OPA v0.x"})," syntax),\nand ",(0,s.jsx)(n.code,{children:"1"})," corresponds to v1 Rego (current OPA v1.x syntax).\nIf the field is not included in the manifest, OPA will enforce v1 syntax, or v0 if executed with\nthe ",(0,s.jsx)(n.code,{children:"--v0-compatible"})," flag.\nAn existing bundle ",(0,s.jsx)(n.code,{children:"rego_version"})," field takes precedence to the ",(0,s.jsx)(n.code,{children:"--v0-compatible"})," flag."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"file_rego_versions"})," - An optional field that specifies per-file rego-version overrides to the\n",(0,s.jsx)(n.code,{children:"rego_version"})," field. The value of this field is a ",(0,s.jsx)(n.code,{children:"map"})," where the keys are file paths relative to the\nbundle root directory (paths are absolute and start with ",(0,s.jsx)(n.code,{children:"/"}),") and the values are ",(0,s.jsx)(n.code,{children:"integer"})," rego-versions.\nGlob patterns are accepted, to allow for a single entry to apply to multiple files. The behaviour is undefined\nfor overlapping patterns. If a file is not matched by any pattern, the ",(0,s.jsx)(n.code,{children:"rego_version"})," field is used.\nExisting bundle ",(0,s.jsx)(n.code,{children:"rego_version"})," and ",(0,s.jsx)(n.code,{children:"file_rego_versions"})," fields takes precedence to the ",(0,s.jsx)(n.code,{children:"--v0-compatible"})," flag."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"roots"})," - If you expect to load additional data into OPA from outside the\nbundle (e.g., via OPA's HTTP API) you should include a top-level\n",(0,s.jsx)(n.code,{children:"roots"})," field containing of path prefixes that declare the scope of\nthe bundle. See the section below on managing data from multiple\nsources. If the ",(0,s.jsx)(n.code,{children:"roots"})," field is not included in the manifest it\ndefaults to ",(0,s.jsx)(n.code,{children:'[""]'})," which means that ALL data and policy must come\nfrom the bundle."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"wasm"})," - A list of OPA WebAssembly (Wasm) module files in the bundle along with\nmetadata for how they should be evaluated. The following keys are supported:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"entrypoint"})," - A string path defining what query path the wasm module is\nbuilt to evaluate. Once loaded any usage of this path in a query will use\nthe Wasm module to compute the value."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"module"})," - A string path to the Wasm module relative to the root of the bundle."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"metadata"})," - An optional key that contains arbitrary metadata to accompany the\nbundle. This metadata is available for querying using ",(0,s.jsx)(n.code,{children:"data.system"}),", along with the\nrest of the manifest."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["For example, this manifest specifies a revision (which happens to be a G
1it\ncommit hash) and a set of roots for the bundle contents. In this case, the\nmanifest declares that it owns the roots ",(0,s.jsx)(n.code,{children:"data.roles"})," and\n",(0,s.jsx)(n.code,{children:"data.http.example.authz"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "revision": "7864d60dd78d748dbce54b569e939f5b0dc07486",\n  "roots": ["roles", "http/example/authz"]\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Another example, this time showing a Wasm module configured for\n",(0,s.jsx)(n.code,{children:"data.http.example.authz.allow"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "revision": "7864d60dd78d748dbce54b569e939f5b0dc07486",\n  "roots": ["roles", "http/example/authz"],\n  "wasm": [\n    {\n      "entrypoint": "http/example/authz/allow",\n      "module": "path/to/policy.wasm"\n    }\n  ]\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["For example, the manifest below specifies the global Rego version for the bundle using the ",(0,s.jsx)(n.code,{children:"rego_version"})," field and\nuses the ",(0,s.jsx)(n.code,{children:"file_rego_versions"})," field for overrides. This manifest describes a bundle that follows the OPA v1.0 syntax\nexpect for policy files ",(0,s.jsx)(n.code,{children:"/policy1.rego"})," and those under the folder ",(0,s.jsx)(n.code,{children:"foo"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "revision": "7864d60dd78d748dbce54b569e939f5b0dc07486",\n  "rego_version": 1,\n  "file_rego_versions": {\n    "/foo/*.rego": 0,\n    "/policy1.rego": 0\n  }\n}\n'})}),"\n",(0,s.jsx)(n.h3,{id:"manifest-json-schema",children:"Manifest JSON Schema"}),"\n",(0,s.jsxs)(n.p,{children:["A machine-readable JSON Schema (Draft 2020-12) describing the manifest format\nis published at\n",(0,s.jsx)(n.a,{href:"https://openpolicyagent.org/schemas/bundle/v1/manifest.schema.json",children:(0,s.jsx)(n.code,{children:"https://openpolicyagent.org/schemas/bundle/v1/manifest.schema.json"})}),".\nThe schema is generated from the Go type definitions in\n",(0,s.jsx)(n.a,{href:"https://github.com/open-policy-agent/opa/blob/main/v1/bundle/bundle.go",children:(0,s.jsx)(n.code,{children:"v1/bundle/bundle.go"})}),"\nand stays in sync with them via a CI drift test, so it always reflects what the\ncurrent ",(0,s.jsx)(n.code,{children:"opa build"})," actually emits. Backward-incompatible changes will be\npublished under a new path (e.g., ",(0,s.jsx)(n.code,{children:"/v2/"}),")."]}),"\n",(0,s.jsxs)(n.p,{children:["Use it to validate manifests, generate typed bindings in non-Go languages, or\nfeed it into JSON Schema-aware tooling. Example with ",(0,s.jsx)(n.a,{href:"https://ajv.js.org/",children:(0,s.jsx)(n.code,{children:"ajv"})}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"opa build -b bundle/\ntar -xzf bundle.tar.gz .manifest\najv validate \\\n  -s https://openpolicyagent.org/schemas/bundle/v1/manifest.schema.json \\\n  -d .manifest\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The top-level ",(0,s.jsx)(n.code,{children:"Manifest"})," object does not set ",(0,s.jsx)(n.code,{children:"additionalProperties: false"}),":\nthe bundle loader has always ignored unknown top-level keys, and embedders\nthat wrap OPA sometimes attach their own configuration alongside the\ndocumented fields. Sub-records like ",(0,s.jsx)(n.code,{children:"WasmResolver"})," remain closed, since\ntheir shape is fully specified."]}),"\n",(0,s.jsx)(n.p,{children:"Some important details for bundle files:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["OPA will only load data files named ",(0,s.jsx)(n.code,{children:"data.json"})," or ",(0,s.jsx)(n.code,{children:"data.yaml"})," (which contain\nJSON or YAML respectively). Other JSON and YAML files will be ignored."]}),"\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.code,{children:"*.rego"})," policy files must be valid ",(0,s.jsx)(n.a,{href:"./policy-language/#modules",children:"Modules"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["OPA will only load Wasm modules named ",(0,s.jsx)(n.code,{children:"policy.wasm"}),". Other WebAssembly binary\nfiles will be ignored."]}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["YAML data loaded into OPA is converted to JSON. Since JSON is a subset of\nYAML, you are not allowed to use binary or null keys in objects and boolean\nand number keys are converted to strings.\n",(0,s.jsxs)(n.a,{href:"https://ref.cod
1dy.tech/yaml/yaml-binary-data",children:["YAML ",(0,s.jsx)(n.code,{children:"!!binary"})," tags"]})," are not\nsupported."]})}),"\n",(0,s.jsx)(n.h2,{id:"multiple-sources-of-policy-and-data",children:"Multiple Sources of Policy and Data"}),"\n",(0,s.jsx)(n.p,{children:"By default, when OPA is configured to download policy and data from a\nbundle service, the entire content of OPA's policy and data cache is\ndefined by the bundle. However, if you need to load OPA with policy\nand data from multiple sources, you can implement your bundle service\nto generate bundles that are scoped to a subset of OPA's policy and\ndata cache."}),"\n",(0,s.jsx)(n.admonition,{type:"danger",children:(0,s.jsxs)(n.p,{children:["Whenever possible, implement policy and data\naggregation centrally. In some cases that's not possible\n(e.g., due to latency requirements.).\nWhen using multiple sources there are ",(0,s.jsx)(n.strong,{children:"no"})," ordering guarantees for which bundle loads first and\ntakes over some root. If multiple bundles conflict, but are loaded at different\ntimes, OPA may go into an error state. It is highly recommended to use\nthe health check and include bundle state: ",(0,s.jsx)(n.a,{href:"./monitoring#health-checks",children:"Monitoring OPA"})]})}),"\n",(0,s.jsxs)(n.p,{children:["To scope bundles to a subset of OPA's policy and data cache, include\na top-level ",(0,s.jsx)(n.code,{children:"roots"})," key in the bundle that defines the roots of the\n",(0,s.jsx)(n.code,{children:"data"})," namespace that are owned by the bundle."]}),"\n",(0,s.jsxs)(n.p,{children:["For example, the following manifest would declare two roots\n(",(0,s.jsx)(n.code,{children:"acmecorp/policy"})," and ",(0,s.jsx)(n.code,{children:"acmecorp/oncall"}),"):"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:'{\n    "roots": ["acmecorp/policy", "acmecorp/oncall"]\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"If OPA was loaded with a bundle containing this manifest it would only\nerase and overwrite policy and data under these roots. Policy and data\nloaded under other roots is left intact."}),"\n",(0,s.jsx)(n.p,{children:"When OPA loads scoped bundles, it validates that:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["The roots are not overlapping (e.g., ",(0,s.jsx)(n.code,{children:"a/b/c"})," and ",(0,s.jsx)(n.code,{children:"a/b"})," are\noverlapped and will result in an error.) Note: This is ",(0,s.jsx)(n.em,{children:"not"}),"\nenforced across multiple bundles. Only within the same bundle\nmanifest."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["The policies in the bundle are contained under the roots. This is\ndetermined by inspecting the ",(0,s.jsx)(n.code,{children:"package"})," statement in each of the\npolicy files. For example, given the manifest above, it would be an\nerror to include a policy file containing ",(0,s.jsx)(n.code,{children:"package acmecorp.other"}),"\nbecause ",(0,s.jsx)(n.code,{children:"acmecorp.other"})," is not contained in either of the roots."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"The data in the bundle is contained under the roots."}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"If bundle validation fails, OPA will report the validation error via\nthe Status API."}),"\n",(0,s.jsx)(n.h2,{id:"debugging-your-bundles",children:"Debugging Your Bundles"}),"\n",(0,s.jsx)(n.p,{children:"When you run OPA, you can provide bundle files over the command line. This\nallows you to manually check that your bundles include all of the files that\nyou intended and that they are structured correctly. For example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"opa run bundle.tar.gz\n"})}),"\n",(0,s.jsx)(n.h2,{id:"signing",children:"Signing"}),"\n",(0,s.jsx)(n.p,{children:"To ensure the integrity of policies (i.e. the policies are coming from a trusted source), policy bundles may be\ndigitally signed so that industry-standard cryptographic primitives can verify their authenticity."}),"\n",(0,s.jsxs)(n.p,{children:["OPA supports digital signatures for policy bundles. Specifically, a signed bundle is a normal OPA bundle that includes\na file named ",(0,s.jsx)(n.code,{children:".signatures.json"})," that dictates which files should be included in the bundle, what their SHA hashes are,\n
1and is cryptographically secure."]}),"\n",(0,s.jsx)(n.p,{children:"When OPA receives a new bundle, it checks that it has been properly signed using a (public) key that OPA has been\nconfigured with out-of-band. Only if that verification succeeds does OPA activate the new bundle; otherwise, OPA\ncontinues using its existing bundle and reports an activation failure via the status API and error logging."}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["Bundle signature verification works differently depending on how bundles are loaded. Filesystem bundles (",(0,s.jsx)(n.code,{children:"--bundle"})," flag) use the ",(0,s.jsx)(n.code,{children:"--verification-key"})," CLI flag pointing to a PEM file. Remote bundles define keys in the configuration file under the ",(0,s.jsx)(n.code,{children:"keys"})," section. Sub-commands primarily used in pre-production (such as ",(0,s.jsx)(n.code,{children:"opa eval"}),", ",(0,s.jsx)(n.code,{children:"opa test"}),", etc.) do not verify bundle signatures at this point in time."]})}),"\n",(0,s.jsx)(n.h3,{id:"signature-format",children:"Signature Format"}),"\n",(0,s.jsxs)(n.p,{children:["Recall that a ",(0,s.jsx)(n.a,{href:"#bundle-file-format",children:"policy bundle"})," is a gzipped tarball that contains policies and data. A signed bundle\ndiffers from a normal bundle in that it has a ",(0,s.jsx)(n.code,{children:".signatures.json"})," file as well."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"$ tar tzf bundle.tar.gz\n.manifest\n.signatures.json\nroles\nroles/bindings\nroles/bindings/data.json\n"})}),"\n",(0,s.jsx)(n.p,{children:"The signatures file is a JSON file with an array of JSON Web Tokens (JWTs) that encapsulate the signatures for the bundle.\nCurrently, you will be limited to one signature, as shown below. In the future, support for multiple signatures may be added\nto sign different files within the bundle."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "signatures": [\n    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmaWxlcyI6W3sibmFtZSI6Ii5tYW5pZmVzdCIsImhhc2giOiJjMjEzMTU0NGM3MTZhMjVhNWUzMWY1MDQzMDBmNTI0MGU4MjM1Y2FkYjlhNTdmMGJkMWI2ZjRiZDc0YjI2NjEyIiwiYWxnb3JpdGhtIjoiU0hBMjU2In0seyJuYW1lIjoicm9sZXMvYmluZGluZ3MvZGF0YS5qc29uIiwiaGFzaCI6IjQyY2ZlNjc2OGI1N2JiNWY3NTAzYzE2NWMyOGRkMDdhYzViODEzNTU0ZWJjODUwZjJjYzM1ODQzZTcxMzdiMWQifV0sImlhdCI6MTU5MjI0ODAyNywiaXNzIjoiSldUU2VydmljZSIsImtleWlkIjoibXlQdWJsaWNLZXkiLCJzY29wZSI6IndyaXRlIn0.ZjtUgXC6USwmhv4XP9gFH6MzZwpZrGpAL_2sTK1P-mg"\n  ]\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The JWT has the standard headers ",(0,s.jsx)(n.code,{children:"alg"})," (for algorithm), ",(0,s.jsx)(n.code,{children:"typ"})," (always JWT), and ",(0,s.jsx)(n.code,{children:"kid"})," (for key id). It has a JSON payload of the\nfollowing form:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "files": [\n    {\n      "name": ".manifest",\n      "hash": "c2131544c716a25a5e31f504300f5240e8235cadb9a57f0bd1b6f4bd74b26612",\n      "algorithm": "SHA-256"\n    },\n    {\n      "name": "roles/bindings/data.json",\n      "hash": "42cfe6768b57bb5f7503c165c28dd07ac5b813554ebc850f2cc35843e7137b1d",\n      "algorithm": "SHA-256"\n    }\n  ]\n}\n'})}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Field"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Required"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"files[_].name"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Yes"}),(0,s.jsx)(n.td,{children:"Path of a file in the bundle."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"files[_].hash"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Yes"}),(0,s.jsx)(n.td,{children:"Output of the hashing algorithm applied to the file."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"files[_].algorithm"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"Yes"}),(0,s.jsx)(n.td,{children:"Name of the hashing algorithm."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"scope"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"No"}),(0,s.jsx)(n.td,{children:"Represents the fragment of signings."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"iat"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"No"}),(0,s.jsx)(n.td,{children:"Time of signature creation since epoch in seconds. For informational purposes only."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"iss"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"string"})}),(0,s.jsx)(n.td,{children:"No"}),(0,s.jsx)(n.td,{children:"Identifies the issuer of the JWT. For informational purposes only."})]})]})]}),"\n",(0,s.jsxs)(n.admonition,{type:"info",children:[(0,s.jsxs)(n.p,{children:["OPA will first look for the ",(0,s.jsx)(n.code,{children:"keyid"})," on the command-line. If the ",(0,s.jsx)(n.code,{children:"keyid"})," is empty, OPA will look for it in it's\nconfiguration. If ",(0,s.jsx)(n.code,{children:"keyid"})," is still empty, OPA will finally look for ",(0,s.jsx)(n.code,{children:"kid"})," in the JWT header."]}),(0,s.jsxs)(n.p,{children:["To include additional claims in the JWT payload such as ",(0,s.jsx)(n.code,{children:"scope"}),", ",(0,s.jsx)(n.code,{children:"iat"}),", ",(0,s.jsx)(n.code,{children:"iss"})," use the ",(0,s.jsx)(n.code,{children:"--claims-file"})," flag\nin the ",(0,s.jsx)(n.code,{children:"opa build"})," or ",(0,s.jsx)(n.code,{children:"opa sign"})," commands to provide a JSON file containing optional claims. See ",(0,s.jsx)(n.code,{children:"opa build --help"}),"\nor ",(0,s.jsx)(n.code,{children:"opa sign --help"})," for more details."]})]}),"\n",(0,s.jsx)(n.p,{children:"The following hashing algorithms are supported:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-txt",children:"MD5\nSHA-1\nSHA-224\nSHA-256\nSHA-384\nSHA-512\nSHA-512-224\nSHA-512-25\n"})}),"\n",(0,s.jsx)(n.p,{children:"To calculate the digest for unstructured files (i.e. all files except JSON or YAML files), apply the hash\nfunction to the byte stream of the file."}),"\n",(0,s.jsx)(n.p,{children:"For structured files, read the byte stream and parse into a JSON structure; then recursively order the fields of all\nobjects alphabetically and then apply the hash function to the result to compute the hash. This ensures\nthat the digital signature is independent of whitespace and other non-semantic JSON features."}),"\n",(0,s.jsxs)(n.p,{children:["To generate a ",(0,s.jsx)(n.code,{children:".signatures.json"})," file for policy and data files that will be part of a bundle, see the ",(0,s.jsx)(n.code,{children:"opa sign"})," command."]}),"\n",(0,s.jsx)(n.h4,{id:"signature-verification",children:"Signature Verification"}),"\n",(0,s.jsxs)(n.p,{children:["When OPA receives a policy bundle that doesn't include the ",(0,s.jsx)(n.code,{children:".signatures.json"})," file and the bundle is not configured to\nuse a signature, OPA does not perform signature verification and activates the bundle just as it always has."]}),"\n",(0,s.jsxs)(n.p,{children:["If the actual bundle contains the ",(0,s.jsx)(n.code,{children:".signatures.json"})," file but the bundle is not configured to use a signature, verification fails."]}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.th,{children:[(0,s.jsx)(n.code,{children:".signatures.json"})," exists"]}),(0,s.jsx)(n.th,{children:"bundle configured to verify signature"}),(0,s.jsx)(n.th,{children:"verification performed"}),(0,s.jsx)(n.th,{children:"result"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"no"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"no"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"no"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"NA"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"no"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"yes"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"yes"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"fail"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"yes"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"no"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"yes"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"fail"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"yes"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"yes"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"yes"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"depends on the verification steps described below"})})]})]})]}),"\n",(0,s.jsxs)(n.p,{children:["When OPA receives a signed bundle it opens the ",(0,s.jsx)(n.code,{children:".signatures.json"})," file, grabs the JWT and performs the following steps:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Verify the JWT signature with the appropriate public key"}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Verify that the JWT payload and target directory specify the same set of files"}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Verify the content of each file by checking the hash recorded in the JWT payload is the same as the hash generated\nfor that file"}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"OPA activates the new bundle only if all the verification steps succeed;
1 otherwise, it continues using its existing bundle\nand reports an activation failure via the status API and error logging."}),"\n",(0,s.jsx)(n.p,{children:"The signature verification process uses each of the fields in the JWT header and payload as follows:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"files"}),": This list of files in the payload must match exactly the files in the bundle, and for each file the hash of the file must match"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"kid"}),": If supplied in the header, dictates which key (and algorithm) to use for verification. The actual key is supplied via\nOPA out-of-band"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"scope"}),": If supplied in the payload, must match exactly the value provided out-of-band to OPA"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"iat"}),": unused for verification even if present in payload"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"iss"}),": unused for verification even if present in payload"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"signature-plugin",children:"Signature Plugin"}),"\n",(0,s.jsxs)(n.p,{children:["OPA supports the option to implement your own bundle signing and verification logic. This will be unnecessary\nfor most and is intended for advanced use cases, such as leveraging key-related services from cloud providers.\nTo implement your own signing and verification logic, you'll need to ",(0,s.jsx)(n.a,{href:"./extensions",children:"extend OPA"}),". Here is\n",(0,s.jsx)(n.a,{href:"https://github.com/open-policy-agent/contrib/tree/main/custom_bundle_signing",children:"an example"})," to get you started."]}),"\n",(0,s.jsx)(n.p,{children:"When registering custom signing and verification plugins, you will need to register the Signer and the Verifier\nunder the same plugin key, because the plugin key is stored in the signed bundle and informs OPA which Verifier\nis capable of verifying the bundle, e.g."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-go",children:'bundle.RegisterSigner("custom", &CustomSigner{})\nbundle.RegisterVerifier("custom", &CustomVerifier{})\n'})}),"\n",(0,s.jsx)(n.h2,{id:"delta-bundles",children:"Delta Bundles"}),"\n",(0,s.jsxs)(n.p,{children:["A regular ",(0,s.jsx)(n.em,{children:"snapshot"})," bundle represents the entirety of OPA\u2019s policy and data cache. When a new ",(0,s.jsx)(n.em,{children:"snapshot"})," bundle is\ndownloaded, OPA will erase and overwrite all the policy and data in its cache before activating the new bundle. The bundle can\noptionally be scoped to a subset of OPA\u2019s policy and data cache by defining the ",(0,s.jsx)(n.code,{children:"roots"})," in the bundle\u2019s ",(0,s.jsx)(n.code,{children:".manifest"})," file."]}),"\n",(0,s.jsxs)(n.p,{children:["Although OPA ",(0,s.jsx)(n.a,{href:"#caching",children:"caches"})," snapshot bundles to avoid unnecessary retransmission,\nservers must still retransmit the entire snapshot when any change occurs. If you need\nto propagate small changes to bundles without waiting for polling delays, consider\nusing ",(0,s.jsx)(n.em,{children:"delta"})," bundles in conjunction with ",(0,s.jsx)(n.a,{href:"#http-long-polling",children:"HTTP Long Polling"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.em,{children:"Delta"})," bundles provide a more efficient way to make data changes by containing patches to data instead of complete snapshots.\n",(0,s.jsx)(n.em,{children:"Delta"})," bundles are structured differently from ",(0,s.jsx)(n.em,{children:"snapshot"})," bundles. A ",(0,s.jsx)(n.em,{children:"delta"})," bundle contains a\nsingle ",(0,s.jsx)(n.code,{children:"patch.json"})," file at the root of the bundle which includes a ",(0,s.jsx)(n.a,{href:"https://datatracker.ietf.org/doc/html/rfc6902",children:"JSON Patch"}),"\n(i.e., an array of one or more JSON objects). The operations in the JSON Patch will be applied to OPA's in-memory store in order."]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.em,{children:"Delta"})," bundles currently support updates to data only and not policies."]})}),"\n",(0,s.jsx)(n.h3,{id:"delta-bundle-file-format",children:"Delta Bundle File Format"}),"\n",(0,s.jsxs)(n.p,{children:["OPA expects a ",(0,s.jsx)(n.em,{children:"delta"})," bundle to contain an optional ",(0,s.jsx)(n.code,{children:".manifest"})," file and a required ",(0,s.jsx)(n.code,{children:"patch.json"})," file that specifies a list of one or more\npatch operations on the data. OPA will generate an error if a ",(0,s.jsx)(n.em,{children:"delta"})," bundle contains any policy, data or wasm binary files.\nIf the ",(0,s.jsx)(n.code,{children:".manifest"})," file specifies any ",(0,s.jsx)(n.code,{children:"roots"}),", any data patch outside the bundle's roots will cause an error."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"$ tar tzf bundle.tar.gz\n.manifest\npatch.json\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Below is an example of the ",(0,s.jsx)(n.code,{children:"patch.json"})," file:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "data": [\n    { "op": "upsert", "path": "/a/b", "value": ["hello", "world"] },\n    { "op": "remove", "path": "/a/c" }\n  ]\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["If OPA has previously activated a ",(0,s.jsx)(n.em,{children:"snapshot"})," bundle that did not contain a .manifest file, then the ",(0,s.jsx)(n.em,{children:"delta"})," bundle\nmust not contain a ",(0,s.jsx)(n.code,{children:".manifest"})," file."]}),"\n",(0,s.jsxs)(n.p,{children:["If OPA has a previously activated ",(0,s.jsx)(n.em,{children:"snapshot"})," bundle that did contain a ",(0,s.jsx)(n.code,{children:".manifest"})," file, then the ",(0,s.jsx)(n.em,{children:"delta"})," bundle may\ncontain a ",(0,s.jsx)(n.code,{children:".manifest"})," file. Specifically if a previously activated ",(0,s.jsx)(n.em,{children:"snapshot"})," bundle contains a ",(0,s.jsx)(n.code,{children:".manifest"})," file that\ndeclares ",(0,s.jsx)(n.code,{children:"roots"})," or ",(0,s.jsx)(n.code,{children:"wasm"})," fields, a ",(0,s.jsx)(n.em,{children:"delta"})," bundle update MUST have the same values for the manifest\n",(0,s.jsx)(n.code,{children:"roots"})," and ",(0,s.jsx)(n.code,{children:"wasm"})," fields from the original ",(0,s.jsx)(n.em,{children:"snapshot"})," bundle. This means a ",(0,s.jsx)(n.em,{children:"delta"})," bundle cannot be used to change\nthe scope of the original bundle or update Wasm resolvers. A ",(0,s.jsx)(n.em,{children:"delta"})," bundle can however contain different\nvalues for the bundle's ",(0,s.jsx)(n.code,{children:"revision"})," and ",(0,s.jsx)(n.code,{children:"metadata"}),"."]}),"\n",(0,s.jsxs)(n.admonition,{type:"danger",children:[(0,s.jsxs)(n.p,{children:["An empty list of operations in a ",(0,s.jsx)(n.em,{children:"delta"})," bundle ",(0,s.jsx)(n.code,{children:"patch.json"})," will remove all the data from OPA's in-memory store. I.e., the following are equivalent:"]}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "data": []\n}\n'})}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "data": [\n    { "op": "replace", "path": "/", "value": {} }\n  ]\n}\n'})}),(0,s.jsxs)(n.p,{children:["If there are no operations to apply to the data, the bundle server should return the same ",(0,s.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag",children:(0,s.jsx)(n.code,{children:"Etag"})})," value as the last update. OPA will send the last ",(0,s.jsx)(n.code,{children:"Etag"})," value in the ",(0,s.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-None-Match",children:(0,s.jsx)(n.code,{children:"If-None-Match"})})," Header."]})]}),"\n",(0,s.jsx)(n.h4,{id:"delta-bundle-patch-operations",children:"Delta Bundle Patch Operations"}),"\n",(0,s.jsxs)(n.p,{children:["Each patch operation defined in the ",(0,s.jsx)(n.code,{children:"patch.json"})," file must have exactly one ",(0,s.jsx)(n.code,{children:"op"})," member which indicates the\noperation to perform. Valid options include:"]}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"op"}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"remove"'})}),(0,s.jsxs)(n.td,{children:["The ",(0,s.jsx)(n.code,{children:'"path"'})," specified will be removed from OPA's in-memory store. The ",(0,s.jsx)(n.code,{children:'"value"'})," field is ignored for ",(0,s.jsx)(n.code,{children:'"remove"'})," operations. The target path must exist for the operation to be successful."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"replace"'})}),(0,s.jsxs)(n.td,{children:["The value at the specified ",(0,s.jsx)(n.code,{children:'"path"'})," will be replaced by the new value defined by the ",(0,s.jsx)(n.code,{children:'"value"'})," field. The target path must exist for the operation to be successful."]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:'"upsert"'})}),(0,s.jsxs)(n.td,{children:["The ",(0,s.jsx)(n.code,{children:'"value"'})," will be set at the specified ",(0,s.jsx)(n.code,{children:'"path"'}),". If the ",(0,s.jsx)(n.code,{children:'"path"'})," specifies an array index, the ",(0,s.jsx)(n.code,{children:'"value"'}
1)," is inserted into the array at the specified index. If the ",(0,s.jsx)(n.code,{children:'"path"'})," specifies an object member that does not already exist, a new member is added to the object. If the object member exists, its value is replaced. If the ",(0,s.jsx)(n.code,{children:'"path"'})," does not exist, OPA will create and add it to its in-memory store."]})]})]})]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"upsert"})," operation in not part of the ",(0,s.jsx)(n.a,{href:"https://datatracker.ietf.org/doc/html/rfc6902",children:"JSON Patch"})," standard."]})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:'"path"'})," field defines a JSON pointer path to the location to perform the operation on."]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:'"value"'})," field defines the value to be added or replaced. Only required for ",(0,s.jsx)(n.code,{children:'"upsert"'})," and ",(0,s.jsx)(n.code,{children:'"replace"'})," operations."]}),"\n",(0,s.jsx)(n.h4,{id:"current-limitations",children:"Current Limitations"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.em,{children:"Delta"})," bundles only support updates to data. Policies cannot be updated using ",(0,s.jsx)(n.em,{children:"delta"})," bundles."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.em,{children:"Delta"})," bundles do not support bundle signing."]}),"\n",(0,s.jsxs)(n.li,{children:["Unlike ",(0,s.jsx)(n.em,{children:"snapshot"})," bundles, activated ",(0,s.jsx)(n.em,{children:"delta"})," bundles are not persisted to disk when the ",(0,s.jsx)(n.code,{children:"bundles[_].persist"})," field is ",(0,s.jsx)(n.code,{children:"true"}),"."]}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"delta-bundle-faq",children:"Delta Bundle FAQ"}),"\n",(0,s.jsxs)(n.p,{children:["This section discusses some ",(0,s.jsx)(n.em,{children:"delta"})," bundle usage, edge cases and failure scenarios."]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"What happens if OPA cannot apply a data patch ?"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Bundle activation will fail in this scenario. In the next attempt to download the bundle, OPA will set the value\nof the ",(0,s.jsx)(n.code,{children:"If-None-Match"})," header of the bundle request to the last successful activation Etag value. This should help the\nBundle Service to send the correct revision of the bundle to OPA."]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"What happens if OPA cannot reach the Bundle Service (for example. network failure) or is unable to download a bundle ?"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["OPA always includes the last successful activation Etag value in the bundle request. When OPA eventually reconnects\nwith the server, the value of the ",(0,s.jsx)(n.code,{children:"If-None-Match"})," header of bundle request could be empty indicating that OPA was not\nable to activate the first revision of the bundle itself. This helps the server to re-transmit the correct bundle revision."]}),"\n",(0,s.jsxs)(n.p,{children:["In case OPA has already activated a revision of the bundle, and reaches out to the server with the last\nsuccessful activation Etag value, the server now knows to send the next bundle revision. This could either be a snapshot\nor delta bundle. One possible approach on the server-side, would be to first send a snapshot bundle and then send delta bundles\nto perform data patch operations. The server could maintain the order in which the bundles should go out for example,\nassigning an Etag value to each bundle revision. Hence, it can figure out the right bundle to send by looking up the\n",(0,s.jsx)(n.code,{children:"If-None-Match"})," header of bundle request and then lining-up the next bundle in the queue."]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Does a ",(0,s.jsx)(n.em,{children:"delta"})," bundle always need to be preceded by a ",(0,s.jsx)(n.em,{children:"snapshot"})," bundle ?"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["No. OPA will activate a ",(0,s.jsx)(n.em,{children:"delta"})," bundle if all the patch operations in it were successfully applied. Note that a ",(0,s.jsx)(n.em,{children:"snapshot"}),"\nbundle would erase and overwrite policy and data under the manifest ",(0,s.jsx)(n.code,{children:"roots"}),"."]}),"\n",(0,s.jsx)(n.h2,{id:"implementations",children:"Implementations"}),"\n",(0,s.jsx)(n.p,{children:"The Bundle API is simple. Most HTTP servers capable of serving static files will do. While not strictly required in all deployments, it is also good if the implementation supports:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["HTTP caching using the ",(0,s.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag",children:"ETag header"}),". This keeps OPA from having to download a bundle unless the bundle's content have changes."]}),"\n",(0,s.jsx)(n.li,{children:"Authentication. When exposing a bundle at a remote endpoint, it is often desirable to protect the data by requiring all requests to the endpoint to be authenticated."}),"\n"]}
1),"\n",(0,s.jsx)(n.p,{children:"This document lists some of the more common HTTP servers suitable as bundle servers, along with instructions for how to set them up as such."}),"\n",(0,s.jsx)(n.h3,{id:"amazon-s3",children:"Amazon S3"}),"\n",(0,s.jsx)(n.h4,{id:"opa-bundle-support",children:"OPA Bundle Support"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Feature"}),(0,s.jsx)(n.th,{children:"Supported"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Caching headers"}),(0,s.jsx)(n.td,{children:"Yes"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Authentication methods"}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/latest/configuration/#aws-signature",children:"AWS Signature"})})]})]})]}),"\n",(0,s.jsx)(n.h4,{id:"setup-instructions",children:"Setup Instructions"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:'Search for "S3" and on the "Buckets" page, click "Create bucket".'}),"\n",(0,s.jsx)(n.li,{children:"Fill in the form according to your preferences (name, region, etc)."}),"\n",(0,s.jsx)(n.li,{children:'Either choose "Block all public access" for internal systems, or unmark the checkbox for that to allow external (authenticated) requests.'}),"\n",(0,s.jsx)(n.li,{children:"You can now upload your bundle to the bucket. If you try to download it right away you'll notice that by default you're unauthorized to do so."}),"\n",(0,s.jsx)(n.li,{children:'To allow anyone to read the bundle, click on it and select "Make public" from the "Object actions" dropdown menu. If not, proceed to configure authentication.'}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"authentication",children:"Authentication"}),"\n",(0,s.jsx)(n.p,{children:"Authentication can be configured to either use the credentials of a service account stored in the environment, or to use credentials fetched from the AWS metadata API. The latter is only available from services running inside of AWS (on EC2 or ECS)."}),"\n",(0,s.jsx)(n.p,{children:"Both methods are going to need a policy for either the service account or the IAM role, so when that is mentioned in the steps for either method you may refer to the example below."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Example IAM policy"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "Version": "2012-10-17",\n  "Statement": [\n    {\n      "Effect": "Allow",\n      "Action": [\n        "s3:ListBucket"\n      ],\n      "Resource": [\n        "arn:aws:s3:::my-example-opa-bucket"\n      ]\n    },\n    {\n      "Effect": "Allow",\n      "Action": [\n        "s3:PutObject",\n        "s3:GetObject"\n      ],\n      "Resource": [\n        "arn:aws:s3:::my-example-opa-bucket/*"\n      ]\n    }\n  ]\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"NOTE:"})," The above policy permits both uploads and downloads, which is good for testing. The OPA client however needs only the ",(0,s.jsx)(n.code,{children:"s3:GetObject"})," permission for downloads and should be the only permission granted for production use cases."]}),"\n",(0,s.jsx)(n.h5,{id:"environment-credentials",children:"Environment Credentials"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:'Go to the "IAM" section of the AWS console. Choose "Users" and "Create new user". Select a name for the user, and the "Programmatic access" option.'}),"\n",(0,s.jsxs)(n.li,{children:['On the following "Permissions" page, choose "Attach existing policies directly" and then press "Create policy". Select the JSON tab and paste a policy like the example shown above, replacing ',(0,s.jsx)(n.code,{children:"my-example-opa-bucket"})," with the name of your bucket."]}),"\n",(0,s.jsx)(n.li,{children:"Once the policy has been created, it can be assigned to the user. With the user having been created, make sure to note down the AWS access key ID and the AWS secret access key, as they will be the credentials used for authentication."}),"\n"]}),"\n",(0,s.jsx)(n.h5,{id:"metadata-credentials",children:"Metadata Credentials"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:'Go to the "IAM" section of the AWS console. Choose "Roles" and "Create role". For type, select "AWS service" and for use case, 
1choose EC2, or wherever you\'ll be running OPA.'}),"\n",(0,s.jsxs)(n.li,{children:['On the following "Permissions" page, choose "Create policy". Select the JSON tab and paste a policy like the example shown above, replacing ',(0,s.jsx)(n.code,{children:"my-example-opa-bucket"})," with the name of your bucket."]}),"\n",(0,s.jsx)(n.li,{children:"Once the policy has been created, it can be assigned to the role."}),"\n",(0,s.jsx)(n.li,{children:'With the role created, go to the EC2 instance view. Select an instance where OPA will run and select "Actions" -> "Security" -> "Modify IAM role". Select the role created in previous steps.'}),"\n"]}),"\n",(0,s.jsx)(n.h5,{id:"web-identity-credentials",children:"Web Identity Credentials"}),"\n",(0,s.jsx)(n.p,{children:"Using EKS IAM Roles for Service Account (Web Identity) Credential."}),"\n",(0,s.jsx)(n.p,{children:"Below are steps to use OpenID connect provider and Kubernetes."}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:'Go to the "IAM" section of the AWS console.'}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Click Add provider and select OpenID connect."}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"For Provider URL enter the one belonging to your chosen Kubernetes cluster."}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Click on Get thumbprint"}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"For the audience enter: sts.amazonaws.com"}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Add the provider."}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Once the provider is added, copy the ARN for the identity provider. Here's an example ARN: ",(0,s.jsx)(n.code,{children:"arn:aws:iam::<your AWS account ID>:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/DFGHJKKJHGF34HFDFGHY44TRFDE4RGDF"})]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create an IAM role (e.g., ",(0,s.jsx)(n.code,{children:"app_dev_role"}),") with the policy created above and assign it to the Kubernetes service account."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Go to Trust relationships inside the created role and click Edit trust relationship and enter the following policy document."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "Version": "2012-10-17",\n  "Statement": [\n    {\n      "Effect": "Allow",\n      "Principal": {\n        "Federated": "<the ARN of the Identity provider from step 7, e.g. arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/DFGHJKKJHGF34HFDFGHY44TRFDE4RGDF where 123456789012 is the account ID of your AWS account, and DFGHJK...4RGDF is the OpenID Connect URL\'s end>"\n      },\n      "Action": "sts:AssumeRoleWithWebIdentity",\n      "Condition": {\n        "StringEquals": {\n          "<the OpenID connect URL, e.g. oidc.eks.ap-northeast-1.amazonaws.com/id/B7060B6E991747ADDDC61ADD4B7875CF>:sub": "system:serviceaccount:<Kubernetes namespace, e.g. app-dev>:<the Kubernetes serviceaccount name, eg: app-dev-service-account>"\n        }\n      }\n    }\n  ]\n}\n'})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Create the Kubernetes service account."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  annotations:\n    eks.amazonaws.com/role-arn: <the ARN of the IAM role from your account, e.g. arn:aws:iam::<aws_account eg, 123456789012>:role/app_dev_role>\n  name: <service account name, e.g. app-dev-service-account>\n  namespace: <k8 namespace, e.g. app-dev>\nautomountServiceAccountToken: false\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Configure your Kubernetes resources to use this service account."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  ******\nspec:\n  ******\n  template:\n    *******\n    spec:\n      serviceAccountName: app-dev-service-account # <--- like this\n      automountServiceAccountToken: true\n      containers:\n      ******\n"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"You should now be able to access AWS services from your Kubernetes cluster."}),"\n",(0,s.jsx)(n.p,{children:"The above steps should add the following variable to the pod."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"AWS_ROLE_ARN=<the ARN of the IAM role from your account, e.g. arn:aws:iam::123456789012:role/app_dev_role>\nAWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Please read ",(0,s.jsx)(n.a,{href:"https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html",children:"IAM roles for service accounts"})," for more details."]}),"\n",(0,s.jsx)(n.h5,{id:"testing-authentication",children:"Testing Authentication"}),"\n",(0,s.jsxs)(n.p,{children:["Use the ",(0,s.jsx)(n.a,{href:"https://aws.amazon.com/cli/",children:"AWS CLI tools"})," (see ",(0,s.jsx)(n.a,{href:"#upload-bundle",children:'"Upload Bundle"'})," below)."]}),"\n",(0,s.jsx)(n.h4,{id:"upload-bundle",children:"Upload Bundle"}),"\n",(0,s.jsxs)(n.p,{children:["Bundle uploads to S3 are easily facilitated using the ",(0,s.jsx)(n.code,{children:"aws"})," command in the ",(0,s.jsx)(n.a,{href:"https://aws.amazon.com/cli/",children:"AWS CLI tools"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"aws --profile=opa-service-account s3 cp bundle.tar.gz s3://my-example-opa-bucket/\n"})}),"\n",(0,s.jsx)(n.h4,{id:"example-opa-configuration",children:"Example OPA Configuration"}),"\n",(0,s.jsx)(n.h5,{id:"environment-credentials-1",children:"Environment Credentials"}),"\n",(0,s.jsxs)(n.p,{children:["With the environment variables ",(0,s.jsx)(n.code,{children:"AWS_REGION"}),", ",(0,s.jsx)(n.code,{children:"AWS_ACCESS_KEY_ID"})," and ",(0,s.jsx)(n.code,{children:"AWS_SECRET_ACCESS_KEY"})," set, the following configuration will extract the credentials from the ",(0,s.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/latest/configuration/#using-static-environment-credentials",children:"environment"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"services:\n  s3:\n    url: https://my-example-opa-bucket.s3.eu-north-1.amazonaws.com\n    credentials:\n      s3_signing:\n        environment_credentials: {}\n\nbundles:\n  authz:\n    service: s3\n    resource: bundle.tar.gz\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"NOTE:"})," the S3 ",(0,s.jsx)(n.code,{children:"url"})," is the bucket's regional endpoint."]}),"\n",(0,s.jsx)(n.h5,{id:"metadata-credentials-1",children:"Metadata Credentials"}),"\n",(0,s.jsx)(n.p,{children:'In order for this to work it is required that the permissions you created in the "Authentication" steps above are embedded in an IAM Role, which is then assigned to the EC2 instance hosting OPA.'}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"services:\n  s3:\n    url: https://my-example-opa-bucket.s3.eu-north-1.amazonaws.com\n    credentials:\n      s3_signing:\n        metadata_credentials:\n          aws_region: eu-north-1\n          iam_role: my-opa-bucket-access-role\n\nbundles:\n  authz:\n    service: s3\n    resource: bundle.tar.gz\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"NOTE:"})," the S3 ",(0,s.jsx)(n.code,{children:"url"})," is the bucket's regional endpoint."]}),"\n",(0,s.jsx)(n.h5,{id:"assume-role-credentials",children:"Assume Role Credentials"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"services:\n  s3:\n    url: https://my-example-opa-bucket.s3.us-east-1.amazonaws.com\n    credentials:\n      s3_signing:\n        assume_role_credentials:\n          aws_region: us-east-1\n          iam_role_arn: arn:aws::iam::123456789012:role/demo\n          session_name: my-open-policy-agent # Optional. Default: open-policy-agent\n          aws_signing: # similar to s3_signing\n            metadata_credentials:\n              aws_region: us-east-1\n              iam_role: s3access\n\nbundles:\n  authz:\n    service: s3\n    resource: bundle.tar.gz\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"NOTE:"})," the S3 ",(0,s.jsx)(n.code,{children:"url"})," is the bucket's regional endpoint."]}),"\n",(0,s.jsx)(n.h5,{id:"web-identity-credentials-1",children:"Web Identity Credentials"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"services:\n  s3:\n    url: https://my-example-opa-bucket.s3.eu-north-1.amazonaws.com\n    credentials:\n      s3_signing:\n        web_identity_credentials:\n          aws_region: eu-north-1\n          session_name: my-open-policy-agent # Optional. Default: open-policy-agent\n\nbundles:\n  authz:\n    service: s3\n    resource: bundle.tar.gz\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"NOTE:"})," the S3 ",(0,s.jsx)(n.code,{children:"url"})," is the bucket's regional endpoint."]}),"\n",(0,s.jsx)(n.h5,{id:"credential-provider-chaining",children:"Credential Provider Chaining"}),"\n",(0,s.jsxs)(n.p,{children:["Multiple AWS credential providers can be configured. OPA will follow an ",(0,s.jsx)(n.em,{children:"internally defined"})," order to try each of the credential provider given in the configuration till success. Following order of precedence is followed when multiple credential provider is given in the configuration"]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Environment Credential"}),"\n",(0,s.jsx)(n.li,{children:"Assume Role Credential"}),"\n",(0,s.jsx)(n.li,{children:"Web Identity Credential"}),"\n",(0,s.jsx)(n.li,{children:"Profile Credential"}),"\n",(0,s.jsx)(n.li,{children:"Metadata Credential"}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"services:\n  s3:\n    url: https://my-example-opa-bucket.s3.eu-north-1.amazonaws.com\n    credentials:\n      s3_signing:\n        metadata_credentials:\n          aws_region: eu-north-1\n          iam_role: my-opa-bucket-access-role\n        environment_credentials: {}\n\nbundles:\n  authz:\n    service: s3\n    resource: bundle.tar.gz\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"NOTE:"})," In this example, OPA will look for AWS credentials in the environment first before trying metadata endpoint. S3 signing will fail if none of the providers are successful."]}),"\n",(0,s.jsx)(n.h3,{id:"google-cloud-storage",children:"Google Cloud Storage"}),"\n",(0,s.jsx)(n.h4,{id:"opa-bundle-support-1",children:"OPA Bundle Support"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Feature"}),(0,s.jsx)(n.th,{children:"Supported"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Caching headers"}),(0,s.jsx)(n.td,{children:"Yes"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Authentication methods"}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/latest/configuration/#gcp-metadata-token",children:"GCP Metadata Token"})," ",(0,s.jsx)("br",{})," ",(0,s.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/latest/configuration/#oauth2-jwt-bearer-grant-type",children:"OAuth2 JWT Bearer Grant Type"})]})]})]})]}),"\n",(0,s.jsx)(n.h4,{id:"setup-instructions-1",children:"Setup Instru
1ctions"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:'In the left pane menu, choose "Cloud Storage". Click "New bucket".'}),"\n",(0,s.jsx)(n.li,{children:"Fill in the form according to your preferences (name, region, availability, etc)."}),"\n",(0,s.jsx)(n.li,{children:'Once the bucket is created, you can press "Upload" to upload a test bundle. Clicking this will provide a link to the bundle which you can use in your OPA configuration.'}),"\n",(0,s.jsx)(n.li,{children:'At this stage you can either choose to make the bucket public (by clicking "Permissions") or to configure a service account for authenticated access.'}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"authentication-1",children:"Authentication"}),"\n",(0,s.jsx)(n.h5,{id:"gcp-metadata-token-authentication",children:"GCP Metadata Token Authentication"}),"\n",(0,s.jsxs)(n.p,{children:["If your instance of OPA runs inside GCP, you'll be able to authenticate using GCP metadata tokens. These tokens by default carry all the permissions granted to the default service account, so you might still want to create a dedicated service account for this purpose (see ",(0,s.jsx)(n.a,{href:"#jwt-bearer-grant-type",children:"JWT Bearer Grant Type"})," below)."]}),"\n",(0,s.jsx)(n.h5,{id:"jwt-bearer-grant-type",children:"JWT Bearer Grant Type"}),"\n",(0,s.jsxs)(n.p,{children:["Use this for ",(0,s.jsx)(n.a,{href:"https://docs.cloud.google.com/storage/docs/authentication",children:"authenticating"})," ",(0,s.jsx)(n.em,{children:"external"})," clients, i.e. OPA instances running outside the GCP environment."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:'Search for "credentials" in the top search box and choose "Credentials - APIs and Services".'}),"\n",(0,s.jsx)(n.li,{children:'Click "Create Credentials" followed by "Service Account."'}),"\n",(0,s.jsx)(n.li,{children:"Fill in a name for the account and proceed to select roles."}),"\n",(0,s.jsx)(n.li,{children:'Choose "Storage Object Viewer" for read access and "Storage Object Creator" for write access (if scripted uploads is desired).'}),"\n",(0,s.jsx)(n.li,{children:'Click the newly created service account and then the "Keys" tab. Press "Add Key" and either "Create new" or upload an existing one.'}),"\n",(0,s.jsx)(n.li,{children:"If creating new, choose to download the private key in JSON format (not P12)."}),"\n",(0,s.jsxs)(n.li,{children:["Open the JSON file just downloaded and copy the PEM encoded value of the ",(0,s.jsx)(n.code,{children:"private_key"})," attribute. This is the key you'll use for your OPA configuration."]}),"\n"]}),"\n",(0,s.jsx)(n.h5,{id:"testing-authentication-1",children:"Testing Authentication"}),"\n",(0,s.jsx)(n.p,{children:"To test GCP metadata token or JWT bearer grant type authentication, set up OPA with the relevant config and run the server."}),"\n",(0,s.jsx)(n.h4,{id:"upload-bundle-1",children:"Upload Bundle"}),"\n",(0,s.jsxs)(n.p,{children:["Uploading a bundle is trivial with the ",(0,s.jsx)(n.code,{children:"gsutil"})," command included with the ",(0,s.jsx)(n.a,{href:"https://docs.cloud.google.com/sdk/docs/install-sdk",children:"Google Cloud SDK"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"gsutil cp bundle.tar.gz gs://<bucket-name>/\n"})}),"\n",(0,s.jsx)(n.h4,{id:"example-opa-configuration-1",children:"Example OPA Configuration"}),"\n",(0,s.jsx)(n.h5,{id:"gcp-metadata-token-authentication-1",children:"GCP Metadata Token Authentication"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:'services:\n  gcs:\n    url: https://storage.googleapis.com/storage/v1/b/${BUCKET_NAME}/o\n    credentials:\n      gcp_metadata:\n        scopes:\n        - https://www.googleapis.com/auth/devstorage.read_only\n\nbundles:\n  authz:\n    service: gcs\n    # NOTE ?alt=media is required\n    resource: "bundle.tar.gz?alt=media"\n'})}),"\n",(0,s.jsxs)(n.p,{children:["If the resource (the object in the gcs bucket) contains slashes (/) or other special characters, these need to be url-encoded here, e.g.\n",(0,s.jsx)(n.code,{children:"bundles/bundle.tar.gz?alt=media"})," should be entered as ",(0,s.jsx)(n.code,{children:"bundles%2fbundle.tar.gz?alt=media"}),". Please refer to the ",(0,s.jsx)(n.a,{href:"https://cloud.google.com/storage/docs/request-endpoints#encoding",children:"official documentation"})," for more information."]}),"\n",(0,s.jsx)(n.h5,{id:"google-cloud-storage-bundle-and-jwt-bearer-authentication",children:"Google Cloud Storage Bundle and JWT Bearer Authentication"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:'services:\n  gcp:\n    url: https://storage.googleapis.com/storage/v1/b/${BUCKET_NAME}/o\n    credentials:\n      oauth2:\n        grant_type: jwt_bearer\n        token_url: https://oauth2.googleapis.com/token\n        signing_key: jwt_signing_key # references the key in `keys` below\n        scopes:\n        - https://www.googleapis.com/auth/devstorage.read_only\n        additional_claims:\n          aud: https://oauth2.googleapis.com/token\n          iss: [email protected]\n\nbundles:\n  authz:\n    service: gcp\n    # NOTE ?alt=media is required\n    resource: "bundle.tar.gz?alt=media"\n\nkeys:\n  jwt_signing_key:\n    algorithm: RS256\n    private_key: ${BUNDLE_SERVICE_SIGNING_KEY}\n'})}),"\n",(0,s.jsx)(n.h3,{id:"azure-blob-storage",children:"Azure Blob Storage"}),"\n",(0,s.jsx)(n.h4,{id:"opa-bundle-support-2",children:"OPA Bundle Support"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Feature"}),(0,s.jsx)(n.th,{children:"Supported"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Caching headers"}),(0,s.jsx)(n.td,{children:"Yes"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Authentication methods"}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/latest/configuration/#oauth2-client-credentials",children:"OAuth2 Client Credentials"}),", ",(0,s.jsx)("br",{})," ",(0,s.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/latest/configuration/#oauth2-client-credentials-jwt-authentication",children:"OAuth2 Client Credentials JWT authentication"})]})]})]})]}),"\n",(0,s.jsxs)(n.p,{children:["Note that for the time being, the ",(0,s.jsx)(n.a,{href:"https://learn.microsoft.com/en-us
1/rest/api/storageservices/authorize-requests-to-azure-storage",children:"Shared Key or Shared Access Signature (SAS)"})," options are ",(0,s.jsx)(n.a,{href:"https://github.com/open-policy-agent/opa/issues/2964",children:"not supported"}),"."]}),"\n",(0,s.jsx)(n.h4,{id:"setup-instructions-2",children:"Setup Instructions"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Any type of storage in Azure is grouped in Storage Accounts. If you have one already, skip to step 3."}),"\n",(0,s.jsx)(n.li,{children:'From the Azure console, select "Storage Accounts" followed by "New". Fill in the form (name, region, etc) according to your preferences. One thing to note when selecting "account kind", make sure to pick the Storage V2 (general purpose v2) option and not the legacy BlobStorage kind.'}),"\n",(0,s.jsx)(n.li,{children:'With the storage account deployed, press "Go to resource" to create a new storage resource.'}),"\n",(0,s.jsx)(n.li,{children:'Select "Containers" and press the plus sign to create a new storage container.'}),"\n",(0,s.jsx)(n.li,{children:'Name your container and select access level. Choose "Private" to require authentication, or "Blob" to allow unauthenticated read access.'}),"\n",(0,s.jsx)(n.li,{children:'Press "upload" and select the bundle from your local filesystem.'}),"\n",(0,s.jsx)(n.li,{children:"Clicking the filename should bring up a properties window where the public URL to the bundle is included."}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"authentication-2",children:"Authentication"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Go to Azure Active Directory."}),"\n",(0,s.jsx)(n.li,{children:'In the left menu, click "App Registrations" followed by "New Registration". Name your app (client) amd leave the other options be. Click "Register".'}),"\n",(0,s.jsxs)(n.li,{children:['Click "Certificates and Secrets". Either create a secret to be used for ',(0,s.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/latest/configuration/#oauth2-client-credentials",children:"OAuth2 Client Credentials"})," or upload a certificate for ",(0,s.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/latest/configuration/#oauth2-client-credentials-jwt-authentication",children:"OAuth2 Client Credentials JWT authentication"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:['In the menu to the left, click "API permissions". Click "Add a permission". Choose "Azure Storage" and check the ',(0,s.jsx)(n.code,{children:"user_impersonation"})," checkbox."]}),"\n",(0,s.jsx)(n.li,{children:'Click "Add admin consent for Default Directory". Answer Yes on the followup question.'}),"\n",(0,s.jsx)(n.li,{children:'Navigate back to your storage account. Click "Access Control (IAM)". Click "Add role assignments".'}),"\n",(0,s.jsx)(n.li,{children:'Select the "Storage Blob Data Contributor" role. Leave "Assign access to" as "User, group or service principal". Search and select the name of the app created in step 2.'}),"\n",(0,s.jsxs)(n.li,{children:['Configuration is now complete. Go back to "App Registrations" in the Active Directory view to check details like tenant ID, application ID and endpoints. You\'ll need those when configuring OPA (see ',(0,s.jsx)(n.a,{href:"#example-opa-configuration",children:"Example Configuration"})," below)."]}),"\n"]}),"\n",(0,s.jsx)(n.h5,{id:"testing-authentication-2",children:"Testing Authentication"}),"\n",(0,s.jsx)(n.p,{children:"Use Curl to test client authentication with a secret."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:'curl --silent \\\n     --data "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&scope=https://storage.azure.com/.default" \\\n     "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token"\n'})}),"\n",(0,s.jsx)(n.h4,{id:"upload-bundle-2",children:"Upload Bundle"}),"\n",(0,s.jsxs)(n.p,{children:["Uploading bundles to Azure Blob storage is easily done using the ",(0,s.jsx)(n.a,{href:"https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azcopy-v10",children:"azcopy"})," tool. Make sure to first properly ",(0,s.jsx)(n.a,{href:"https://learn.microsoft.com/en-us
1/azure/storage/common/storage-use-azcopy-authorize-user-identity",children:"authorize"})," the user to be able to upload to Blob storage."]}),"\n",(0,s.jsxs)(n.p,{children:["By now you should be able to login interactively using ",(0,s.jsx)(n.code,{children:"azcopy login --tenant-id <Active Directory tenant ID>"}),". Since you'll most likely will want to log in from scripts (to upload bundles programmatically), you should however create an Azure AD application, and a ",(0,s.jsx)(n.a,{href:"https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal",children:"service principal"})," to do so. Good news! If you've followed the Authentication steps above, you already have one."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Uploading bundle using client secret authentication"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"AZCOPY_SPA_CLIENT_SECRET='<application_client_secret>' azcopy login \\\n  --service-principal \\\n  --tenant-id <tenant-id> \\\n  --application-id <application-id>\n\nazcopy copy bundle.tar.gz https://<storage-account-id>.blob.core.windows.net/<container-id>/bundle.tar.gz\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Uploading bundle using client certificate authentication"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"AZCOPY_SPA_CERT_PASSWORD='<client_cert_password>' azcopy login \\\n  --service-principal \\\n  --tenant-id <tenant-id> \\\n  --certificate-path <path-to-certificate-file> --tenant-id <tenant-id>\n\nazcopy copy bundle.tar.gz https://<storage-account-id>.blob.core.windows.net/<container-id>/bundle.tar.gz\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Uploading bundle using Curl"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:'token=$(curl --silent \\\n             --data "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&scope=https://storage.azure.com/.default" \\\n             "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" | jq -r .access_token)\n\ncurl --silent \\\n     -X PUT \\\n     --data-binary "@bundle.tar.gz" -H "X-Ms-Version: 2020-04-08" -H "Authorization: Bearer $token" \\\n     https://styra.blob.core.windows.net/opa/bundle.tar.gz\n'})}),"\n",(0,s.jsx)(n.h4,{id:"example-opa-configuration-2",children:"Example OPA Configuration"}),"\n",(0,s.jsx)(n.h5,{id:"azure-blob-storage-bundle-and-client-credentials-authentication",children:"Azure Blob Storage Bundle and Client Credentials Authentication"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:'services:\n  blob:\n    url: https://my-storage-account.blob.core.windows.net\n    headers:\n      # This header _must_ be present in all authenticated requests\n      x-ms-version: "2020-04-08"\n    credentials:\n      oauth2:\n        token_url: "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token"\n        client_id: "${CLIENT_ID}"\n        client_secret: "${CLIENT_SECRET}"\n        scopes:\n        - https://storage.azure.com/.default\n\nbundles:\n  authz:\n    service: blob\n    resource: my-container/bundle.tar.gz\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Note that the ",(0,s.jsx)(n.code,{children:"$CLIENT_ID"}),' is what is referred to as the "Application ID" inside your Azure account.']}),"\n",(0,s.jsx)(n.h5,{id:"azure-blob-storage-bundle-and-client-credentials-jwt-authentication",children:"Azure Blob Storage Bundle and Client Credentials JWT Authentication"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:'keys:\n  blob_key:\n    algorithm: RS256\n    private_key: "${PRIVATE_KEY_PEM}"\n\nservices:\n  blob:\n    url: https://my-storage-account.blob.core.windows.net\n    headers:\n      # This header _must_ be present in all authenticated requests\n      x-ms-version: "2020-04-08"\n    credentials:\n      oauth2:\n        token_url: "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token"\n        signing_key: blob_key\n        thumbprint: "8F1BDDDE9982299E62749C20EDDBAAC57F619D04"\n        include_jti_claim: true\n        scopes:\n        - https://storage.azure.com/.default\n        additional_claims:\n          aud: "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token"\n          iss: "${CLIENT_ID}"\n          sub: "${CLIENT_ID}"\n\nbundles:\n  authz:\n    service: blob\n    resource: opa/bundle.tar.gz\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Note that the ",(0,s.jsx)(n.code,{children:"$CLIENT_ID"}),' is what is referred to as the "Application ID" inside your Azure account.\nAlso note in particular how the ',(0,s.jsx)(n.code,{children:"thumbprint"}),' property is required for Azure. The value expected here can be found under "Certificates and Secrets" in your application\'s configuration.']}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Certificate thumbprint",src:i(16954).A+"",width:"1902",height:"520"})}),"\n",(0,s.jsx)(n.h3,{id:"nginx",children:"Nginx"}),"\n",(0,s.jsx)(n.p,{children:"Nginx offers a simple but competent bundle server for those who prefer to host their own and is also suitable for local testing."}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Feature"}),(0,s.jsx)(n.th,{children:"Supported"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Caching headers"}),(0,s.jsx)(n.td,{children:"Yes"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Authentication methods"}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/latest/configuration/#bearer-token",children:"Bearer Token"})," ",(0,s.jsx)("sup",{children:"1"}),(0,s.jsx)("br",{})," ",(0,s.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/latest/configuration/#oauth2-client-credentials-jwt-authentication",children:"OAuth2 Client Credentials JWT authentication"})," ",(0,s.jsx)("sup",{children:"2"})]})]})]})]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)("sup",{children:"1"}),"Nginx does not support bearer token authentication, but it does support ",(0,s.jsx)(n.a,{href:"https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/",children:"basic auth"}),". This can be achieved by setting ",(0,s.jsx)(n.code,{children:"services[_].credentials.bearer.scheme"})," to ",(0,s.jsx)(n.code,{children:"Basic"})," in the OPA configuration, and providing the base64 encoded credentials as the token.",(0,s.jsx)("br",{}),"\n",(0,s.jsx)("sup",{children:"2"}),"Only available with Nginx Plus."]}),"\n",(0,s.jsx)(n.h4,{id:"upload-bundle-3",children:"Upload Bundle"}),"\n",(0,s.jsxs)(n.p,{children:["Either use the ",(0,s.jsx)(n.a,{href:"https://docs.nginx.com/",children:"nginx-upload-module"})," or upload bundles out-of-band with SSH or similar."]}),"\n",(0,s.jsx)(n.h4,{id:"example-opa-configuration-3",children:"Example OPA Configuration"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"services:\n  nginx:\n    url: https://my-nginx.example.com\n    credentials:\n      bearer:\n        token: dGVzdGluZzp0ZXN0aW5n\n        scheme: Basic\n\nbundles:\n  authz:\n    service: nginx\n    resource: /bundle.tar.gz\n"})}),"\n",(0,s.jsx)(n.h3,{id:"oci-registry",children:"OCI Registry"}),"\n",(0,s.jsxs)(n.p,{children:["OPA is able to interact with ",(0,s.jsx)(n.a,{href:"https://opencontainers.org/",children:"OCI"})," compatible registries to be able to download and use policies stored as containers.\nTo configure OPA to use an OCI repository see the ",(0,s.jsx)(n.a,{href:"./configuration/#services",children:"service configuration section"})]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Structure"}),"\nThe bundle container is composed of 3 layers:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"the manifest layer - contains the information about the tarball layer of the container(the digest, size, mediatype and annotations) and the config layer"}),"\n",(0,s.jsx)(n.li,{children:"the bundle tarball layer - the actual bundle tarball"}),"\n",(0,s.jsx)(n.li,{children:"the configuration layer - currently empty"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["For OCI compatible registries an ",(0,s.jsx)(n.em,{children:(0,s.jsx)(n.strong,{children:"oci"})})," folder is created in the ",(0,s.jsx)(n.a,{href:"./configuration/#miscellaneous",children:"persistence directory"}),". If this value is not set, because the OCI downloader plugin requires a storage path, the system's temporary folder location will be used instead. This folder should be maintained by the user. Back up or clean up this folder periodically as this acts as a local cache for the OCI downloader."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Current Limitations"}),"\nThe OCI Downloader plugin used by OPA has a couple of limitation:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["it accepts only ",(0,s.jsx)(n.strong,{children:"one"})," layer per image that contains the bundle tarball"]}),"\n",(0,s.jsxs)(n.li,{children:["it can download only the following application media types:","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"application/vnd.oci.image.layer.v1.tar+gzip"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"application/vnd.oci.image.manifest.v1+json"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"application/vnd.oci.image.config.v1+json"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h4,{id:"building-and-publishing-policy-containers",children:"Building and Publishing Policy Containers"}),"\n",(0,s.jsx)(n.p,{children:"There are multiple ways to build an image from a policy code base using different tools."}),"\n",(0,s.jsx)(n.h5,{id:"using-opa-and-oras-clis",children:"Using OPA and ORAS CLIs"}),"\n",(0,s.jsxs)(n.p,{children:["To build and push a policy bundle to a remote OCI registry with the\n",(0,s.jsx)(n.a,{href:"./cli/",children:"OPA CLI"}),"\nand ",(0,s.jsx)(n.a,{href:"https://oras.land/docs/installation/",children:"ORAS CLI"})," you can use the following\ncommands:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"opa build <path_to_src>"})," will allow you to build a bundle tarball from your OPA policy and data files"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Provide a config manifest to the ORAS CLI and the tarball itself:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"oras push <registry>/<org>/<repo>:<tag> --manifest-config <you_config_json>:application/vnd.oci.image.config.v1+json <the_tarball_obtained_from_opa_build>:application/vnd.oci.image.layer.v1.tar+gzip"})}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Using an empty(",(0,s.jsx)(n.code,{children:"{}"}),") ",(0,s.jsx)(n.code,{children:"manifest-config"})," json file should be sufficient to be able to push and allow the OCI downloader to use the remote policy image."]}),"\n",(0,s.jsx)(n.h4,{id:"maintaining-a-policy-as-code-repository",children:"Maintaining a policy-as-code repository"}),"\n",(0,s.jsx)(n.p,{children:"One of the easiest method of managing your policy bundles is to store your co
1de base in a hosted repository service like GitHub or GitLab and set up an automated way to build and publish your code as a container to the desired registry using a CI(ex. GitHub Action)."}),"\n",(0,s.jsx)(n.h4,{id:"example",children:"Example"}),"\n",(0,s.jsxs)(n.p,{children:["In this example, the ",(0,s.jsx)(n.a,{href:"https://ghcr.io",children:"ghcr.io"})," OCI registry is used as the upstream repository and the OPA and ORAS CLI as the build and publishing tool."]}),"\n",(0,s.jsx)(n.h5,{id:"starting-from-scratch",children:"Starting from scratch"}),"\n",(0,s.jsx)(n.p,{children:"Set up a basic policy example structured as:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"\u2514\u2500\u2500 src\n    \u251c\u2500\u2500 data.json\n    \u251c\u2500\u2500 .manifest\n    \u2514\u2500\u2500 policies\n        \u2514\u2500\u2500 hello.rego\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.em,{children:"hello.rego"})," file contains a very simple example:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",children:'package policies.play\n\ndefault hello = false\n\nhello {\n    m := input.message\n    m == "world"\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.em,{children:".manifest"})," file specifies the root only as:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "roots": ["policies"],\n  "metadata": {\n    "required_builtins": {\n      "builtin1": []\n    }\n  }\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["And the ",(0,s.jsx)(n.em,{children:"data.json"})," file is empty json:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"{}\n"})}),"\n",(0,s.jsx)(n.h6,{id:"building-your-policy",children:"Building your policy"}),"\n",(0,s.jsx)(n.p,{children:"To build the bundle tarball, use the OPA 
1CLI and run the following command:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"opa build .src/\n"})}),"\n",(0,s.jsx)(n.h6,{id:"pushing-the-container-to-a-remote-registry",children:"Pushing the container to a remote registry"}),"\n",(0,s.jsx)(n.p,{children:"Prepare an empty config.json file that contains:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"{}\n"})}),"\n",(0,s.jsx)(n.p,{children:"To push the build image to an upstream registry, first log in using:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"oras login ghcr.io\n"})}),"\n",(0,s.jsx)(n.p,{children:"Push the policy using:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"oras push ghcr.io/someorg/policy-hello:1.0.0 --config config.json:application/vnd.oci.image.config.v1+json bundle.tar.gz:application/vnd.oci.image.layer.v1.tar+gzip\n"})}),"\n",(0,s.jsx)(n.h6,{id:"spin-up-the-policy-with-opa-cli",children:"Spin up the policy with OPA CLI"}),"\n",(0,s.jsx)(n.p,{children:"With the image pushed, prepare the OPA configuration."}),"\n",(0,s.jsx)(n.p,{children:"In this example the configuration.yaml looks like this. The pushed image is private, so credentials are needed for OPA to download it:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:'services:\n  ghcr-registry:\n    url: https://ghcr.io\n    type: oci\n    credentials:\n      bearer:\n        scheme: "Bearer"\n        token: "<mytoken>"\n\nbundles:\n  authz:\n    service: ghcr-registry\n    resource: ghcr.io/someorg/policy-hello:1.0.0\n    persist: true\n    polling:\n      min_delay_seconds: 30\n      max_delay_seconds: 120\n'})}),"\n",(0,s.jsx)(n.p,{children:"In the above configuration, the 1.0.0 tag of the image is pinned. OPA will identify this image by the tag and the descriptor SHA. If the SHA of the image is changed upstream, OPA will redownload and activate the changes."}),"\n",(0,s.jsxs)(n.p,{children:["Running the ",(0,s.jsx)(n.em,{children:"opa CLI"})," with this configuration opens an interactive terminal (REPL) showing the loaded bundle:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"opa run -c configuration.yaml\n"})}),"\n",(0,s.jsx)(n.p,{children:"The terminal should show that the bundle has been loaded and activated:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:'> {"level":"info","msg":"Bundle loaded and activated successfully.","name":"authz","plugin":"bundle","time":"2022-06-15T16:50:53+03:00"}\n> data\n{\n  "policies": {\n    "play": {\n      "hello": false\n    }\n  }\n}\n> exit\n'})}),"\n",(0,s.jsx)(n.p,{children:"Start OPA as a server using:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"opa run --server --set default_decision=policies -c configuration.yaml\n"})}),"\n",(0,s.jsxs)(n.p,{children:["To interact with the server you can do a simple ",(0,s.jsx)(n.strong,{children:"curl"})," to verify if it works as intended:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:'curl localhost:8181 -i -d \'{ "message":"world"}\' -H \'Content-Type:application/json\'\n\nHTTP/1.1 200 OK\nContent-Type: application/json\nDate: Wed, 15 Jun 2022 13:55:19 GMT\nContent-Length: 23\n\n{"play":{"hello":true}}\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:'curl localhost:8181 -i -d \'{ "message":"other"}\' -H \'Content-Type:application/json\'\nHTTP/1.1 200 OK\nContent-Type: application/json\nDate: Wed, 15 Jun 2022 13:56:13 GMT\nContent-Length: 24\n\n{"play":{"hello":false}}\n'})}),"\n",(0,s.jsx)(n.h2,{id:"ecosystem-projects",children:"Ecosystem Projects"}),"\n",(0,s.jsx)(t,{feature:"opa-bundles",children:(0,s.jsx)(n.p,{children:"The Bundle API supports managing policies and data. The following\nprojects all make use of this API if you're looking for inspiration or examples\nof how to use it."})})]})}function h(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},16954:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/thumbprint-b621d36dcbb5f271056dc85dae89577f.png"},28453:(e,n,i)=>{i.d(n,{R:()=>l,x:()=>o});var t=i(96540);const s={},a=t.createContext(s);function l(e){const n=t.useContext(a);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:l(e.components),t.createElement(a.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.