Skip to main content

Network & Proxy Configuration

Enterprise environments often require that all outbound traffic from applications passes through a corporate proxy, firewall, or API gateway. Align provides two layers of network control for self-hosted deployments.

Layer 1: HTTP Proxy (Network-Level)​

Route outbound traffic through a corporate HTTP/HTTPS proxy. The chart sets the proxy variables on the gateway, brain, UI and connector pods. It does not set them on the migrations, bootstrap, RLS cutover or telemetry Jobs, or on the bundled PostgreSQL, Redis, OTel collector and LLM server.

Configuration​

global:
proxy:
# Forward proxy for HTTPS traffic (most common)
httpsProxy: "http://proxy.corp.example.com:3128"
# Forward proxy for HTTP traffic (optional, usually same as above)
httpProxy: "http://proxy.corp.example.com:3128"
# Additional hosts to bypass the proxy (comma-separated)
# Internal Align services are automatically excluded
noProxy: "*.internal.corp.com,10.0.0.0/8"

When configured, those pods receive HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables (both uppercase and lowercase for compatibility). Internal service-to-service traffic is automatically added to NO_PROXY.

How It Works​

  • Connectors (Slack, Teams, Jira, GitHub, Confluence, Zoom) use undici.fetch which respects HTTPS_PROXY automatically
  • Teams Bot Framework SDK has explicit proxy agent support wired in - all token acquisition and proactive messaging traffic is routed through the proxy
  • Brain service (Python/FastAPI) respects HTTPS_PROXY via httpx and aiohttp
  • Gateway respects proxy vars for any outbound calls (credential exchange, etc.)

Backward Compatibility​

When no proxy values are configured, no environment variables are injected. Existing deployments are unaffected.

Layer 2: API Base URL Overrides (Application-Level)​

For more granular control, you can redirect each connector's API traffic to a specific endpoint - for example, a corporate API gateway, reverse proxy, or custom MCP server that wraps the upstream API.

Per-Connector Configuration​

connectors:
slack:
# Route all Slack API calls through your corporate gateway
apiBaseUrl: "https://api-gateway.corp.example.com/slack/api"

teams:
# Route all Microsoft Graph API calls through your corporate gateway
graphApiBaseUrl: "https://api-gateway.corp.example.com/graph"

jira:
# Route all Jira/Atlassian API calls through your corporate gateway
apiBaseUrl: "https://api-gateway.corp.example.com/atlassian/ex/jira"

github:
# GitHub Enterprise Server or corporate gateway
apiBaseUrl: "https://github.corp.example.com/api/v3"

linear:
# Route Linear API calls through your corporate gateway
apiBaseUrl: "https://api-gateway.corp.example.com/linear/graphql"

Environment Variables​

Each override maps to an environment variable:

ConnectorHelm ValueEnvironment VariableDefault
Slackconnectors.slack.apiBaseUrlSLACK_API_BASE_URLhttps://slack.com/api
Teamsconnectors.teams.graphApiBaseUrlTEAMS_GRAPH_API_BASE_URLhttps://graph.microsoft.com
Jiraconnectors.jira.apiBaseUrlJIRA_API_BASE_URLhttps://api.atlassian.com/ex/jira
GitHubconnectors.github.apiBaseUrlGITHUB_API_BASE_URLhttps://api.github.com
Linearconnectors.linear.apiBaseUrlLINEAR_API_BASE_URLhttps://api.linear.app/graphql
tip

You can also set these via extraEnv on each connector if you prefer managing environment variables directly.

note

For Jira, the base URL must include the /ex/jira path prefix. The connector appends /{cloudId}/rest/api/3/... to this base. If routing through a gateway, ensure it proxies to api.atlassian.com/ex/jira with the correct path structure.

Kubernetes NetworkPolicy​

For defense-in-depth, enable Kubernetes NetworkPolicies to apply a default-deny ingress posture and restrict egress at the network level.

What enabled: true ships​

Ingress (default-deny + allow-list):

  • Every Align pod denies ingress by default.
  • Align pods may receive traffic from other Align pods in the same namespace (so gateway↔brain, ui→gateway, and connectors↔gateway keep working).
  • Only the public-facing components (gateway, ui, connector-align) accept external ingress, and only from the source you configure (below).

Egress:

  • Pod-to-pod within the namespace and DNS (UDP/TCP 53, when allowDNS: true).
  • External egress to allowedExternalCIDRs (empty = allow all external).
  • The cloud metadata endpoint (169.254.169.254, AWS IPv6 equivalent) is always blocked.

Configuration​

networkPolicies:
enabled: true
ingress:
# Selector-routable controllers (e.g. ingress-nginx)
fromIngressController:
enabled: true
namespaceSelector:
kubernetes.io/metadata.name: ingress-nginx
podSelector: {}
# ALB/NLB with target-type=ip: traffic arrives from VPC IPs, not a pod.
# Allow your VPC / load-balancer subnet CIDR(s) instead.
allowedIngressCIDRs:
- 10.0.0.0/16
allowDNS: true
allowedExternalCIDRs:
- 10.0.0.0/8
- 172.16.0.0/12
Choose the right ingress source for your load balancer

With an AWS ALB/NLB using target-type: ip (the default in this chart), requests reach pods directly from the load balancer's VPC IPs - a podSelector will not match. Use allowedIngressCIDRs with your VPC CIDR. Use fromIngressController only for in-cluster, selector-routable controllers like ingress-nginx. If you configure neither, the public components accept no external ingress (internal-only).

Rollout​

NetworkPolicies are only enforced under a policy-capable CNI (Calico, Cilium, EKS VPC-CNI with policy enabled); on other clusters they are silently ignored. Because a too-tight allow-list can break ingress, enable on a non-prod environment first, confirm the app is reachable and gateway↔brain works, then promote to prod.

Protect the brain service only​

The brain service has no authentication of its own. It trusts the x-tenant-id header, so network isolation is its boundary. networkPolicies.brain.enabled: true ships one policy that selects only the brain pod. It admits the gateway and the connectors that call brain, from the same namespace, and nothing else. It does not touch any other pod and it does not need networkPolicies.enabled.

networkPolicies:
brain:
enabled: true
# Default list: gateway, connector-jira, connector-confluence, connector-zoom,
# connector-linear. Add a peer here if you run another caller.
extraFrom: []

The default is false. Like every NetworkPolicy, it is ignored by a CNI that does not enforce them. It composes with networkPolicies.enabled: when both are on, the any-Align-pod allow rule skips the brain pod, so the brain list is not cancelled. A caller outside the chart (a one-off job, for example) is not on the list and needs an entry in extraFrom. Test it on a non-production cluster first: from a pod outside the list, a call to the brain service must time out.

Check that the cluster enforces it​

The chart proves the policy objects (scripts/check-brain-networkpolicy-rendered.sh renders the chart and checks that brain admits exactly the pods that call it). It cannot prove your CNI enforces them. A policy object that exists is not a policy that works. Check on the live cluster, in the target namespace, after enabling the policy:

# Must succeed (200): the gateway is on the list.
kubectl -n <ns> exec deploy/<release>-gateway -- \
node -e 'fetch("http://<release>-brain:8090/health").then(r=>console.log(r.status))'
# Must time out or be refused: the ui is not on the list.
kubectl -n <ns> exec deploy/<release>-ui -- \
node -e 'fetch("http://<release>-brain:8090/health",{signal:AbortSignal.timeout(5000)}).then(r=>console.log("REACHED",r.status),e=>console.log("blocked:",e.name))'

If the second call prints REACHED 200, the CNI is not enforcing NetworkPolicies. On EKS the VPC CNI add-on needs network policy support turned on.

Pod-to-pod encryption (mTLS)​

NetworkPolicies restrict who can talk to whom, not whether traffic is encrypted. In-cluster gateway↔brain traffic is plain HTTP. For encryption in transit between pods, run Align inside a service mesh that provides automatic mTLS - Linkerd (simplest) or Istio in mTLS STRICT mode. Both wrap the existing services with no application changes; combine the mesh's mTLS with these NetworkPolicies for full transit hardening.

Required Outbound Domains​

If you need to configure a firewall allowlist, here are the external domains Align connects to:

DomainUsed ByPurpose
slack.comSlack connectorSlack API, OAuth
graph.microsoft.comTeams connectorMicrosoft Graph API
login.microsoftonline.comTeams connectorAzure AD authentication
login.botframework.comTeams connectorBot Framework token service
smba.trafficmanager.netTeams connectorTeams bot messaging
api.atlassian.comJira connectorJira Cloud API
api.github.comGitHub connectorGitHub API
api.linear.appLinear connectorLinear GraphQL API
api.openai.comBrain serviceOpenAI LLM (if configured), and embeddings on the default profile
api.anthropic.comBrain serviceAnthropic LLM (if configured)
The host in license.checkin.url (not published until the licence service is live)GatewayThe daily licence check-in: licence id, install id, version and people covered, signed. On by default for a license with an install key. Off with license.checkin.enabled: false, or with an offline license. Goes through global.proxy. See Licensing
docker.ioBundled Postgres and Redis, telemetry jobsOnly if you use them. The list is not complete: see OAuth apps for the connector hosts

Hosts that are off by default, and what turns each on​

A default install calls ONE host that Align runs: the licence check-in host in the table above (ALI-1950). The hosts below are not contacted by a default install (ALI-1926 checked PostHog, Resend and Grafana Cloud specifically; the connector and LLM hosts above are the ones you configure). Each needs a deliberate step from you:

HostWhat turns it on
eu.i.posthog.com (PostHog)ui.analytics.enabled: true, which only Align's hosted environments set. Do not create the align-posthog secret.
api.resend.com (Resend)A RESEND_API_KEY in the align-resend secret, which is your own Resend account. Sign-in codes and links, and the notification, lead and summary emails.
Grafana Cloud OTLP endpointobservability.grafanaCloud.enabled: true with your own endpoint. Off by default.

The https://align.tech/... addresses in the gateway source (pricing, demo and terms links in emails, reports and error messages, and the licence issuer string) are text a person can click. The server does not request them.

Combining Both Layers​

For maximum security, use both layers together:

global:
proxy:
httpsProxy: "http://squid.corp.example.com:3128"

connectors:
slack:
apiBaseUrl: "https://api-gw.corp.example.com/slack/api"
teams:
graphApiBaseUrl: "https://api-gw.corp.example.com/graph"
jira:
apiBaseUrl: "https://api-gw.corp.example.com/atlassian/ex/jira"
github:
apiBaseUrl: "https://api-gw.corp.example.com/github"

networkPolicies:
enabled: true
allowDNS: true
allowedExternalCIDRs:
- 10.0.0.0/8

This ensures:

  1. All outbound HTTP traffic routes through your proxy (Layer 1)
  2. Each connector's API calls go through your API gateway (Layer 2)
  3. Kubernetes network policy blocks any unexpected egress (defense-in-depth)