Skip to content

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=true and BUNDLE_PATH set to the full path inside the container (e.g., /input/test.tar.gz).

  • BUNDLE_PATH must point to the exact .tar.gz file. If missing/invalid and OFFLINE_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.json file. If data.json is updated, the changes will not be automatically applied to the appliance while it is in Offline mode. To incorporate the updated data.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 OK once 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 OK only 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

Terminal window
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:version

Docker 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: 6

Additionally, 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_healthy

Or 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_healthy

Example 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: 8080

An 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

  1. Docker Desktop installed
  2. Kubernetes enabled on Docker Desktop
  3. kubectl installed on your favorite cli. The previous link has information on Windows, or you may install it on wsl
  4. Istio installed with the demo profile on your local kubernetes cluster, along with istioctl
  5. TAM executable downloaded and available in your folder/cli

Protect server with TAM appliance using Istio

  1. Save istio.rego and istio_test.rego
istio.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"
}
istio_test.rego
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" } } }
}
  1. 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>
  2. 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>
  3. Create namespace for testing
    kubectl create ns foo

  4. Tell Istio to pay attention to your new namespace
    kubectl label ns foo istio-injection=enabled

  5. Save nginx-deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
name: api-deployment
spec:
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: v1
kind: Service
metadata:
name: api-service
spec:
selector:
app: api
ports:
- protocol: TCP
port: 80
targetPort: 80
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-allow-configmap
data:
allow.html: |
<html>You should be allowed to see this content</html>
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-deny-configmap
data:
deny.html: |
<html>You should NOT be allowed to see this content</html>
  1. Deploy an nginx server we can use for testing with the file you just saved
    kubectl apply -f nginx-deployment.yml -n foo

  2. Save appliance-deployment.yml

apiVersion: v1
kind: Service
metadata:
name: ext-authz
labels:
app: ext-authz
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app: ext-authz
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ext-authz
spec:
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"
  1. Deploy the TAM appliance
    kubectl apply -n foo -f appliance-deployment.yml

  2. 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

  3. Save authorizer-service.yml

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: external-authz-http-local
spec:
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
  1. Register the service to the Istio mesh kubectl apply -n foo -f authorizer-service.yml

  2. 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"
  1. Save nginx-authz.yml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ext-authz
spec:
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/*"]
  1. Enable Authorization on the nginx service
    kubectl apply -n foo -f nginx-authz.yml

  2. Save istio-gateway.yml

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: my-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: my-virtualservice
spec:
hosts:
- "*"
gateways:
- my-gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
port:
number: 80
host: api-service
  1. Enable gateway to api
    kubectl apply -n foo -f istio-gateway.yml

  2. 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

  3. 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+
  1. Use the curl below to test nginx. You should see: Welcome to nginx!
    curl --location 'localhost/' --header 'Authorization: <Bearer your_token_here>

  2. 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>

  3. 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: v1
kind: Service
metadata:
name: appliance
spec:
selector:
app: appliance
ports:
- protocol: TCP
port: 7001
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: appliance
spec:
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: 3

Of 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.