Modes
title: “Run Appliance” description: “Appliance Run information.” lead: “The appliance will need specific configuration variables to be set in order to run.” date: 2020-10-13T15:21:01+02:00 lastmod: 2020-10-13T15:21:01+02:00 draft: false images: [] menu: docs: parent: “how-to” weight: 203 toc: true
Modes
An appliance can run in multiple configurations:
- REST API
- Envoy Authorization Filter
Additionally, you can choose how to run the appliance itself as a docker container, ECS sidecar, or even as part of a service mesh. Some example configurations are provided below, but you may need to invest time and consideration as to how your infrastructure should be orchestrated.
Configuration
Ports
The Appliance will need a port exposed for the REST API and/or the Envoy API. This will allow communication from your other containers that need to ask for authorization decisions through the Appliance API. The default ports for the appliance container are as follows:
- API_PORT: 8080
Environment Variables
The appliance needs mutliple variables to be set in order to run:
Environment (See: Environments):
- Ex:
ENVIRONMENT=Stage
REST / Envoy API Port:
- Ex:
API_PORT=8080
Application Client ID (from Trimble Cloud Console):
- Ex:
CLIENT_ID=b34857c1-6553-4909-beb3-5328e1810c48
Application Client Secret (from Trimble Cloud Console):
- Ex:
CLIENT_SECRET=6ff7cac0bb6a449eb8597ba1ba2952c3
Optional Redis Cache (for Remote Data Caching)
- Ex:
CACHE_TYPE=redis - Ex:
REDIS_CONNECTION_URL=redis://default:password@redis:6379/0
Optional AWS lambda invoke
- Ex:
ENABLE_LAMBDA_CLIENT=true - Ex:
AUTH_DATA_LAMBDA={lambda_name_to_invoke}
default connection must be established for aws connectivity between Appliance machine and AWS account.
Optional Secret Manager (loading secret from aws ssm)
- Ex:
SECRET_MANAGER=ssm - Ex:
CLIENT_ID=/iam/dev/client_id - Ex:
CLIENT_SECRET=/iam/dev/client_secret
Optional Partition (for handling different stacks)
- Ex:
PARTITION=us-east
Optional log-level (for handling different LOG levels)
- Ex:
LOG_LEVEL=DEBUG
Optional Offline Bundle (for offline policy loading)
- Ex:
OFFLINE_MODE=true - Ex:
BUNDLE_PATH=/input/test.tar.gz
Notes for Optional Offline mode:
-
Download the bundle from the TAM server and save the .tar.gz locally.
-
Mount the file into the container, e.g.
-v "C:\path\to\bundle.tar.gz:/input/test.tar.gz". -
Start the container with
OFFLINE_MODE=trueandBUNDLE_PATHset to the full path inside the container (e.g.,/input/test.tar.gz). -
BUNDLE_PATHmust point to the exact .tar.gz file. If missing/invalid andOFFLINE_MODE=true, the appliance will exit. -
When the appliance is operating in Offline mode, it will only load the policy bundle from the specified local path during startup. The appliance will not attempt to connect to the TAM server for updates.
-
This behavior also applies to the
data.jsonfile. Ifdata.jsonis updated, the changes will not be automatically applied to the appliance while it is in Offline mode. To incorporate the updateddata.json, the policy bundle must be re-deployed, after which the appliance will pick up the new data.
Appliance Health
The appliance has a health endpoint on its API_PORT at /health.
GET /health/livez
-
This endpoint returns
HTTP 200 OKonce the appliance has started, regardless of whether the policy bundle has been downloaded or the OPA server is running. -
This endpoint indicates that the appliance has successfully started and there are no infrastructure-level issues.
GET /health/readyz
-
Returns
HTTP 200 OKonly after the appliance has successfully downloaded the policy bundle and is fully operational. -
If the appliance cannot communicate with the server to download policy bundles, this endpoint will return
HTTP 503 Service Unavailable. -
This endpoint confirms that the appliance is ready to serve requests based on the loaded policy.
Local Testing / Debugging
The appliance can be run in your local Docker environment in order to test decisions against your policies locally without having an associated API, Envoy Proxy, or other container communicating with the appliance. Both the Docker and Docker compose example expose the appliance REST API on port 8080.
Docker
docker run -it -p 8080:8080 \ -e ENVIRONMENT=Stage \ -e API_PORT=8080 \ -e CLIENT_ID=<<your_client_id>> \ -e CLIENT_SECRET=<<your_client_secret>> \ trimblecloud-tam-appliance-core-docker-local.artifactory.trimble.tools/appliance:versionDocker Compose
version: '3.4'
services: appliance: image: trimblecloud-tam-appliance-core-docker-local.artifactory.trimble.tools/appliance:version ports: - 8080:8080 environment: - ENVIRONMENT=Stage - API_PORT=8080 - CLIENT_ID=<<your_client_id>> - CLIENT_SECRET=<<your_client_secret>> - PARTITION=<<partition>> (Optional) healthcheck: test: wget --no-verbose --tries=1 --spider http://localhost:8080/health/readyz || exit 1 interval: 10s timeout: 10s retries: 6Additionally, you can also run with Docker Compose and your own container side-by-side.
The following example shows the configuration needed to launch an appliance and have its REST API be accessible as http://appliance:7001 to your API testwebservice:
version: '3.4'
services: appliance: image: trimblecloud-tam-appliance-core-docker-local.artifactory.trimble.tools/appliance:version ports: - 7001:8080 environment: - ENVIRONMENT=Stage - API_PORT=8080 - CLIENT_ID=<<your_client_id>> - CLIENT_SECRET=<<your_client_secret>> - PARTITION=<<partition>> (Optional) healthcheck: test: wget --no-verbose --tries=1 --spider http://localhost:7001/health/readyz || exit 1 interval: 10s timeout: 10s retries: 6
testwebservice: image: nginx # Your docker image here. ports: - "8082:80" depends_on: appliance: condition: service_healthyOr you can run with an Envoy proxy in front of your API testwebservice by providing the Docker Compose and a configuration for yaml.
Docker Compose Envoy
version: '3.4'
services: appliance: image: ${DOCKER_REGISTRY-}appliance build: context: Appliance dockerfile: Dockerfile ports: - "7001:8080" environment: - ENVIRONMENT=Stage - API_PORT=8080 - CLIENT_ID=<<your_client_id>> - CLIENT_SECRET=<<your_client_secret>> healthcheck: test: wget --no-verbose --tries=1 --spider http://localhost:7001/health/readyz || exit 1 interval: 10s timeout: 10s retries: 6
envoy: image: envoyproxy/envoy:v1.26-latest ports: - "8000:8000" volumes: - ./envoy/envoy.yaml:/etc/envoy/envoy.yaml # Modify this as you see fit for your own Envoy config. depends_on: appliance: condition: service_healthy
testwebservice: image: nginx # Your docker image here. ports: - "8082:80" depends_on: appliance: condition: service_healthyExample Envoy configuration YAML:
static_resources: listeners: - address: socket_address: address: 0.0.0.0 port_value: 8000 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager codec_type: AUTO stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: upstream domains: - "*" routes: - match: prefix: "/" route: cluster: upstream-service http_filters: - name: envoy.filters.http.ext_authz typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz transport_api_version: V3 http_service: server_uri: uri: ext_authz cluster: ext_authz-http-service timeout: 0.250s path_prefix: "/v1/envoy" authorization_request: headers_to_add: - key: "Rego-Policy-Package-Namespace" value: "<<package_namespace>>" - key: "Rego-Policy-Rule" value: "<<rule>>" - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters: - name: upstream-service type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: upstream-service endpoints: - lb_endpoints: - endpoint: address: socket_address: address: testwebservice port_value: 8082
- name: ext_authz-http-service type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: ext_authz-http-service endpoints: - lb_endpoints: - endpoint: address: socket_address: address: appliance port_value: 8080An external authorization HTTP filter is used in the Envoy configuration above. The filter configuration adds headers (Rego-Policy-Package-Namespace and Rego-Policy-Rule) to specify the package namespace and rule that the appliance uses for the authorization decision. You will need to supply the package namespace and rule for the <<package_namespace>> and <<rule>> values respectively.
Now requests to the Envoy service (http://localhost:8000 in this example) will be filtered by the appliance before passing API calls to testwebservice.
Istio Service Mesh
Note: this example protects a web server from calls by allowing or denying calls, but does not allow API calls that produces lists by modfying data with rego.
Prerequisites
- Docker Desktop installed
- Kubernetes enabled on Docker Desktop
- kubectl installed on your favorite cli. The previous link has information on Windows, or you may install it on wsl
- Istio installed with the demo profile on your local kubernetes cluster, along with istioctl
- TAM executable downloaded and available in your folder/cli
Protect server with TAM appliance using Istio
- Save
istio.regoandistio_test.rego
package tam.istio.example
default allow := false
allow { input.caller_auth.claims.iss == "https://stage.id.trimblecloud.com" input.path == "/auth/allow.html" input.method == "GET"}package tam.istio.example
import future.keywords
test_permission_denied if { not allow with input as {"method": "GET", "path": "/auth/deny.html", "caller_auth" : { "claims": {"iss": "https://stage.id.trimblecloud.com" } } }}
test_permission_allowed if { allow with input as {"method": "GET", "path": "/auth/allow.html", "caller_auth" : { "claims": {"iss": "https://stage.id.trimblecloud.com" } } }}-
Register Package with the files you just saved
- Register your istio policy package. Customize package namespace in rego and the below command or use already registered policy. You will need to modify “Rego-Policy-Package-Namespace” set in the mesh configMap to be the right policy package namespace as well.
./tam package register --packageNamespace tam.istio.example --pathToFiles istio.rego --pathToTestFiles istio_test.rego --environment Stage --consumerKey <consumer_key_goes_here> --consumerSecret <consumer_secret_goes_here>
- Register your istio policy package. Customize package namespace in rego and the below command or use already registered policy. You will need to modify “Rego-Policy-Package-Namespace” set in the mesh configMap to be the right policy package namespace as well.
-
Register Application
- Register your application. Customize package namespace per package registered above.
./tam application register --namespaces tam.istio.example --environment Stage --consumerKey <consumer_key_goes_here> --consumerSecret <consumer_secret_goes_here>
- Register your application. Customize package namespace per package registered above.
-
Create namespace for testing
kubectl create ns foo -
Tell Istio to pay attention to your new namespace
kubectl label ns foo istio-injection=enabled -
Save
nginx-deployment.yml
apiVersion: apps/v1kind: Deploymentmetadata: name: api-deploymentspec: replicas: 1 selector: matchLabels: app: api template: metadata: labels: app: api spec: containers: - name: api image: nginx ports: - containerPort: 80 volumeMounts: - mountPath: /usr/share/nginx/html/auth/allow.html name: nginx-allow-conf subPath: allow.html - mountPath: /usr/share/nginx/html/auth/deny.html name: nginx-deny-conf subPath: deny.html volumes: - name: nginx-allow-conf configMap: name: nginx-allow-configmap - name: nginx-deny-conf configMap: name: nginx-deny-configmap---apiVersion: v1kind: Servicemetadata: name: api-servicespec: selector: app: api ports: - protocol: TCP port: 80 targetPort: 80---apiVersion: v1kind: ConfigMapmetadata: name: nginx-allow-configmapdata: allow.html: | <html>You should be allowed to see this content</html>---apiVersion: v1kind: ConfigMapmetadata: name: nginx-deny-configmapdata: deny.html: | <html>You should NOT be allowed to see this content</html>-
Deploy an nginx server we can use for testing with the file you just saved
kubectl apply -f nginx-deployment.yml -n foo -
Save
appliance-deployment.yml
apiVersion: v1kind: Servicemetadata: name: ext-authz labels: app: ext-authzspec: ports: - name: http port: 8080 targetPort: 8080 selector: app: ext-authz---apiVersion: apps/v1kind: Deploymentmetadata: name: ext-authzspec: replicas: 1 selector: matchLabels: app: ext-authz template: metadata: labels: app: ext-authz spec: containers: - image: trimblecloud-tam-appliance-core-docker-local.artifactory.trimble.tools/appliance:version name: ext-authz ports: - containerPort: 8080 env: - name: ENVIRONMENT value: "Dev" - name: API_PORT value: "8080" - name: CLIENT_ID value: "7a489063-8de0-46eb-aab4-63523e52e74c" - name: CLIENT_SECRET value: "fb72d7eaa3124ffa86538002dc368da1"-
Deploy the TAM appliance
kubectl apply -n foo -f appliance-deployment.yml -
Verify TAM appliance is up and running
kubectl logs "$(kubectl get pod -l app=ext-authz -n foo -o jsonpath={.items..metadata.name})" -n foo -c ext-authz -
Save
authorizer-service.yml
apiVersion: networking.istio.io/v1alpha3kind: ServiceEntrymetadata: name: external-authz-http-localspec: hosts: - "external-authz-http.local" # The service name to be used in the extension provider in the mesh config. endpoints: - address: "127.0.0.1" ports: - name: http number: 8080 # The port number to be used in the extension provider in the mesh config. protocol: HTTP resolution: STATIC-
Register the service to the Istio mesh
kubectl apply -n foo -f authorizer-service.yml -
Edit the mesh config
kubectl edit configmap istio -n istio-system- Add the extension provider - depending on your setup you may have a few in here, or may have one. You’ll need to pay attention and only copy the appropriate chunk (maybe not all the text below).
data: mesh: |- # Add the following content to define the external authorizers. extensionProviders: - name: "sample-ext-authz-http" envoyExtAuthzHttp: service: "ext-authz.foo.svc.cluster.local" port: "8080" includeRequestHeadersInCheck: ["authorization"] includeAdditionalHeadersInCheck: "Rego-Policy-Package-Namespace": "tam.istio.example" "Rego-Policy-Rule": "allow" pathPrefix: "/v1/envoy"- Save
nginx-authz.yml
apiVersion: security.istio.io/v1kind: AuthorizationPolicymetadata: name: ext-authzspec: selector: matchLabels: app: api action: CUSTOM provider: name: sample-ext-authz-http rules: # The rules specify when to trigger the external authorizer. - to: - operation: paths: ["/auth/*"]-
Enable Authorization on the nginx service
kubectl apply -n foo -f nginx-authz.yml -
Save
istio-gateway.yml
apiVersion: networking.istio.io/v1alpha3kind: Gatewaymetadata: name: my-gatewayspec: selector: istio: ingressgateway servers: - port: number: 80 name: http protocol: HTTP hosts: - "*"
---apiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata: name: my-virtualservicespec: hosts: - "*" gateways: - my-gateway http: - match: - uri: prefix: / route: - destination: port: number: 80 host: api-service-
Enable gateway to api
kubectl apply -n foo -f istio-gateway.yml -
Get a JWT
curl -d "grant_type=client_credentials&scope=tam-dev" -u <clientid>:<clientsecret> -H 'Content-Type: application/x-www-form-urlencoded' https://stage.id.trimblecloud.com/oauth/token -
Optional: view logs
Open separate window to show logs for all requests using follow flag:
kubectl logs "$(kubectl get pod -l app=ext-authz -n foo -o jsonpath={.items..metadata.name})" -n foo -c ext-authz -f
- NOTE: all curls should use linux or ps 7+
-
Use the curl below to test nginx. You should see:
Welcome to nginx!
curl --location 'localhost/' --header 'Authorization: <Bearer your_token_here> -
Use curl to see success based on the rego policy. You should see : TODO
curl --location 'localhost/auth/allow.html' --header 'Authorization: <Bearer your_token_here> -
Use curl to see a denial based on the rego policy. You should see: ‘Policy engine rejected the request’
curl --location 'http://localhost/auth/something_not_allowed' --header 'Authorization: <Bearer your_token_here>
Running Remotely
We recommend running the appliance in a Kubernetes or service mesh environment, which allows you the greatest control over ingress and network traffic.
Kubernetes
The following example shows the configuration needed to launch an appliance and have its REST API be accessible as http://appliance:7001 to your API testwebservice:
apiVersion: v1kind: Servicemetadata: name: appliancespec: selector: app: appliance ports: - protocol: TCP port: 7001 targetPort: 8080---apiVersion: apps/v1kind: Deploymentmetadata: name: appliancespec: replicas: 1 selector: matchLabels: app: appliance template: metadata: labels: app: appliance spec: containers: - name: appliance image: trimblecloud-tam-appliance-core-docker-local.artifactory.trimble.tools/appliance:version ports: - containerPort: 8080 env: - name: ENVIRONMENT value: "Dev" - name: API_PORT value: "8080" - name: CLIENT_ID value: "<<your_client_id>>" - name: CLIENT_SECRET value: "<<your_client_secret>>" readinessProbe: httpGet: path: /health/readyz port: 8080 initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 10 successThreshold: 1 failureThreshold: 3Of course the details of this will change based upon how you run ingress into your environment, so you would either need to expose a port manually or have an ingress controller manage traffic.