Welcome to the third installment of the series presenting how to enable external binary access to SAS Cloud Analytics Services (CAS).
This final article focuses on external binary connectivity to CAS where Kubernetes cannot provision load balancers automatically, such as on‑prem or tightly controlled environments. In these cases, you can expose CAS externally with a manually controlled NodePort service, integrated with an external network load balancer, and aligned TLS certificate settings.
You can reference the first article for a description of the architecture, requirements and preliminary checks; the second article walks you through an implementation that leverages the automation provided by the CAS Operator to provision a Kubernetes LoadBalancer Service in cloud environments.
Here you can learn how to plan the external network load balancer endpoint, create a NodePort service, and ensure that CAS certificates include the correct external alias and IP address.
The article ends with a structured troubleshooting guide for common connectivity issues, such as CA trust failures, SAN mismatches, or missing routes, reflecting real‑world scenarios.
Core goal: Address the reality that on‑prem environments often have less automation and more stakeholder boundaries, requiring stricter collaboration between IT infrastructure teams, Kubernetes administrators, and SAS administrators.
The steps presented in the previous article are based on official SAS Viya documentation, which assumes that you can leverage the integrated automation of your cloud provider, Kubernetes cluster and SAS Viya platform to configure external binary access to CAS with simple declarative entries in a configuration file.
A customer reached out to us asking how to obtain the same result in their environment, based on an OpenShift cluster deployed on physical hardware in an on-prem datacenter. Their IT team manages the creation and configuration of network artifacts such as the frontend load balancer and DNS aliases. They require the CAS service to listen on a pre-defined, static port number, so that the frontend load balancer can be configured to redirect incoming connections from external CAS clients to that internal known port.
You might remember from the previous article that, when leveraging the CAS Operator automation, you cannot customize many details of the new CAS service that gets created. The name sas-cas-server-default-bin and the frontend port 5570 are fixed by the CAS Operator. The internal service port is randomly assigned by the Kubernetes cluster and cannot be specified, as well.
All these requirements and constraints can be solved with a customized approach.
The detailed steps might vary from cluster to cluster, according to the environment, company policies, etc.
The rest of this article walks through one sample manual implementation where you bypass CAS Operator automation and perform the same configuration steps as you would with any other Kubernetes service.
The steps described in this article show that it is possible to configure external binary access to CAS even when you have to manually configure all the required components. You might have to involve different teams to support you with the configuration and changes to the infrastructure.
Use the following sequence to plan the manual configuration, validate prerequisites, and confirm external CAS connectivity end to end.
Before touching any configuration files, you will need to retrieve some parameters or decide their value. Here is a high-level list, with some sample choices that we made in our test OpenShift environment.
You need to make sure that the desired DNS alias resolves to the expected IP address. In our case, we use the following code to verify that it maps to the IP address of our OpenShift default router:
CUSTOM_SVC_NAME="cas-server-test"
MY_SUBDOMAIN="viya.example.com"
echo "Looking for IP address of: ${CUSTOM_SVC_NAME}.${MY_SUBDOMAIN}
It should be the same IP address as the OpenShift default router:
$(oc -n openshift-ingress get service router-default -o json | jq -r .status.loadBalancer.ingress[0].ip)"
dig +short "${CUSTOM_SVC_NAME}.${MY_SUBDOMAIN}"
This prints a confirmation like the following:
Looking for IP address of: cas-server-test.viya.example.com
It should be the same IP address as the OpenShift default router:
52.232.224.221
router.apps.viya.example.com.
52.232.224.221
Create the new NodePort service. You can use this sample code; notice that it is mostly a copy of the original sas-cas-server-default-bin service that the CAS Operator creates when using automation:
CUSTOM_NODE_PORT=30076
CUSTOM_SVC_NAME="cas-server-test"
# delete the service if it was previously defined
kubectl -n ${NS} delete service ${CUSTOM_SVC_NAME} --ignore-not-found
# Create the new service on a custom node port
echo "---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/instance: default
app.kubernetes.io/name: sas-cas-server
casoperator.sas.com/instance: default
casoperator.sas.com/server: default
casoperator.sas.com/tenant: shared
sas.com/deployment: sas-viya
sas.com/tenant: shared
name: ${CUSTOM_SVC_NAME}
spec:
type: NodePort
selector:
casoperator.sas.com/controller-active: '1'
casoperator.sas.com/node-type: controller
casoperator.sas.com/server: default
ports:
- name: cal
protocol: TCP
port: 5570
targetPort: 5570
nodePort: ${CUSTOM_NODE_PORT}
" | kubectl -n ${NS} create -f -
After the new service gets created, verify it with the following command:
kubectl -n ${NS} get service ${CUSTOM_SVC_NAME}
Your output should be similar to the following. Notice that NodePort services do not have an external IP.
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cas-server-test NodePort 172.30.218.136 none 5570:30076/TCP 48s
Following the steps described in SAS Viya documentation, copy the sample file to your working directory, and change its permissions so that you can modify it:
cd $deploy
cp ./sas-bases/examples/security/customer-provided-merge-sas-certframe-configmap.yaml \
./site-config/customer-provided-merge-sas-certframe-configmap.yaml
chmod +w ./site-config/customer-provided-merge-sas-certframe-configmap.yaml
Now add the DNS alias and the external IP address to the file, while removing other example placeholders. You can submit the following code to do it automatically using sed:
cd $deploy
MY_ALIAS="cas-server-test.viya.example.com"
MY_IP="$(oc -n openshift-ingress get service router-default -o json | jq -r .status.loadBalancer.ingress[0].ip)"
sed -i -e "s/{{ ADDITIONAL-SAN-DNS-ENTRIES }}/${MY_ALIAS}/g" \
-e "s/{{ ADDITIONAL-SAN-IP-ENTRIES }}/${MY_IP}/g" \
-e "/{{ CERTIFICATE-GENERATOR }}/d" \
-e "/{{ CERTIFICATE-DURATION }}/d" \
-e "/{{ EXCLUDE-MOZILLA-CERTS }}/d" \
./site-config/customer-provided-merge-sas-certframe-configmap.yaml
You can look at the resulting file (using grep to remove comments and yq to color the output):
grep -v '#' ./site-config/customer-provided-merge-sas-certframe-configmap.yaml | yq
Your file should be similar to the following - possibly with different DNS and IP values. Since it is a YAML file, double check that the lines are indented correctly:
---
apiVersion: builtin
kind: ConfigMapGenerator
metadata:
name: sas-certframe-user-config
behavior: merge
literals:
- SAS_CERTIFICATE_ADDITIONAL_SAN_DNS=cas-server-test.viya.example.com
- SAS_CERTIFICATE_ADDITIONAL_SAN_IP=52.232.224.221
After customizing the new ConfigMapGenerator file, update the kustomization.yaml manifest to reference it. Add the following line beneath the existing content in the generators section:
site-config/customer-provided-merge-sas-certframe-configmap.yaml
Finally, redeploy SAS Viya using the same steps as your initial deployment.
Just like with the automated case, when the new settings are in place, you must restart the CAS server pods so that CAS picks up the updated TLS certificate.
We have already validated that the DNS alias resolves to the correct IP address; now we have to check that the new CAS certificate includes both.
Use the following command to list all the aliases defined in the CAS server certificate
kubectl -n ${NS} get secret sas-cas-server-default-controller -o jsonpath="{.data['tls\.crt']}" \
| base64 -d \
| openssl x509 -text -noout \
| grep -B 1 "cas-server-test"
You should get a result similar to the following. Note that it includes both the full DNS alias and the external IP of the cas-server-test service (in this example, cas-server-test.viya.example.com and 52.232.224.221) :
X509v3 Subject Alternative Name:
DNS:banjo-p41772-worker-1, DNS:cas-server-test, DNS:cas-server-test.viya.example.com, DNS:controller, DNS:controller.sas-cas-server-default, DNS:controller.sas-cas-server-default.viya, DNS:controller.sas-cas-server-default.viya.svc.cluster.local, DNS:viya.apps.viya.example.com, DNS:localhost, DNS:sas-cas-server-default, DNS:sas-cas-server-default-client, DNS:sas-cas-server-default-controller, IP Address:10.0.1.5, IP Address:10.128.2.232, IP Address:127.0.0.1, IP Address:172.30.110.241, IP Address:172.30.231.168, IP Address:52.232.224.221
After all the configuration and verification steps have been performed, you can finally connect from an external client. Let’s use the same sample steps as in the previous article.
import swat
username = "MyUserName"
password = "MySecretPassword"
### Insert your DNS alias here!
server = "cas-server-test.viya.example.com"
### Connect to CAS
if server == "cas-server-test.viya.example.com":
raise ValueError(f"Please enter *your* DNS server alias in the server variable and try again")
else:
conn = swat.CAS(server, 5570, username, password)
print(conn.serverStatus())
If you get a printout similar to the following, you have a successful connection:
NOTE: Grid node action status report: 3 nodes, 8 total actions executed.
[About]
{'CAS': 'Cloud Analytic Services', 'Version': '4.00', 'VersionLong': 'V.04.00M0P09082025', 'Viya Release': '20260214.1771047480056', 'Viya Version': 'Long-Term Support 2025.09', 'Copyright': 'Copyright © 2014-2025 SAS Institute Inc. All Rights Reserved.', 'ServerTime': '2026-02-16T17:22:51Z', 'System': {'Hostname': 'controller.sas-cas-server-default.gelcorp.svc.cluster.local', 'OS Name': 'Linux', 'OS Family': 'LIN X64', 'OS Release': '5.14.0-427.107.1.el9_4.x86_64', 'OS Version': '#1 SMP PREEMPT_DYNAMIC Wed Jan 14 07:05:59 EST 2026', 'Model Number': 'x86_64', 'Linux Distribution': 'Red Hat Enterprise Linux release 8.10 (Ootpa)'}, 'license': {'site': '(SIMPLE) THIS ORDER IS FOR SAS INTERNAL USE ONLY', 'siteNum': 70180938, 'expires': '20Mar2026:00:00:00', 'gracePeriod': 0, 'warningPeriod': 15}, 'CASHostAccountRequired': 'OPTIONAL', 'Transferred': 'NO', 'GlobalReadOnlyMode': 'NO', 'CASCacheLocation': 'CAS Disk Cache'}
[server]
Server Status
nodes actions
0 3 8
[nodestatus]
Node Status
name role uptime running stalled
0 worker-0.sas-cas-server-default.gelcorp.svc.c... worker 34.958 0 0
1 worker-1.sas-cas-server-default.gelcorp.svc.c... worker 34.958 0 0
2 controller.sas-cas-server-default.gelcorp.svc... controller 35.016 0 0
+ Elapsed: 0.00614s, user: 0.00141s, sys: 0.00145s, mem: 1.05mb
To simplify troubleshooting, we have collected a few common errors that you may receive when testing from the client, together with possible solutions. You might notice how this section aligns with the earlier validation steps. This troubleshooting applies to both the automation case presented in the previous article, and to the manual case in this article.
In general, when troubleshooting, we suggest following this order:
(1) CAS Service endpoint exists, (2) DNS resolves, (3) SAN in CAS TLS certificate matches, (4) client trusts SAS Viya CA, (5) authentication is valid, (6) network configuration is in place (firewalls/load balancing rules).
The client does not trust the SAS Viya CA
ERROR:
CAUSE: The client is rejecting the CAS server certificate because it does not trust the SAS Viya CA. SOLUTION: Review the steps in the first article to ensure that the client’s TLS trust chain works.
Invalid certificate
ERROR:
CAUSE: The TLS certificate presented by CAS does not include the external hostname or DNS alias used to connect to the server.
SOLUTION: Review the validation in the section Validate the DNS name in the service TLS certificate. If using automation, check the value used in the field publishExtHostnameSuffix of the cas-enable-external-services.yaml file. In the manual case, check the value of SAS_CERTIFICATE_ADDITIONAL_SAN_DNS in the customer-provided-merge-sas-certframe-configmap.yaml file.
Invalid server name
ERROR:
CAUSE: There is an error with the DNS hostname resolution. SOLUTION: Verify that the client connection string is correct (any typo?), and that the DNS entry has been properly created.
Invalid authentication
ERROR:
CAUSE: Authentication failure.
SOLUTION: Verify the username and password entered in the connection string.
Generic connection error
ERROR:
CAUSE: Without any further error messages, this ERROR alone means that there is a generic connection issue between the client and CAS.
SOLUTION: Verify that the CAS server is running, that the LoadBalancer or NodePort service has been created, that the networking load balancer has been configured to route external connections to SAS Viya, and that there are no firewalls or other networking issues blocking the connection.
Manual external binary access to CAS is entirely achievable in environments where Kubernetes cannot provision load balancers automatically, but it succeeds only when service exposure, DNS, and TLS configuration are aligned. In practice, this means creating a NodePort service that the network team can route to, ensuring that the external alias resolves correctly, and updating the CAS certificate so that it matches the hostname and IP address used by clients.
This approach also requires deliberate coordination across teams. The platform administrator owns the Kubernetes service, the infrastructure team owns external routing and DNS, and the SAS Viya administrator owns the CAS certificate updates and client trust validation. When those responsibilities are planned early and validated in order, you can deliver reliable external CAS connectivity even in tightly controlled on-premises environments.
Find more articles from SAS Global Enablement and Learning here.
Visit the Tips & Tricks page for setup guidance, demos, and practical examples that show how Copilot supports your workflows.
The rapid growth of AI technologies is driving an AI skills gap and demand for AI talent. Ready to grow your AI literacy? SAS offers free ways to get started for beginners, business leaders, and analytics professionals of all skill levels. Your future self will thank you.