Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ Where possible, we use [httpbingo.org](https://httpbingo.org) as our REST endpoi
- [postbody](postbody) shows how a POST body can be automatically filled with field arguments with `Content-Type:application/x-www-form-urlencoded`. This is the easiest way to send postbodies down the REST API
- [restWithConfigYaml](restWithConfigYaml) shows how REST query parameters can also be fetched from `config.yaml`--this allows you to keep your SDL code separate from your secrets.
- [restWithParameters](restWithParameters) shows how GraphQL field arguments are automatically added to the REST call--there is nothing for you to do!
- [tls](tls) shows how to use `@rest(tls:)` using the local stepzen service
151 changes: 151 additions & 0 deletions rest/tls/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# TLS


For more inforamtion on using TLS in your REST apis and other services [see our documentation](https://www.ibm.com/docs/en/api-connect-graphql/saas?topic=directives-directive-rest#tls-string__title__1).

[Environment](https://www.ibm.com/docs/en/api-connect-graphql/saas?topic=environment-tls-configuration-properties). Check that the revision is newer than : 2025-11-18.

## Using `@rest(tls:)`

This examples demonstrates a number of StepZen capabilities:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 17 says API Connect for GraphQL, so should we change StepZen to API Connect for GraphQL instead to be consistent across?

- Use of `@rest(tls:)`
- stepzen service
- Simple ecmascript capability for reshaping data.

## mTLS and certificates

API Connect for GraphQL supports mTLS and custom certificates (self-signed, private,
etc.) by using using a combination of a `@rest(tls:)` argument
that refers to a configuration in the `config.yaml`

When the tls entry is given the name of a configuration entry, you can provide
- `ca` - the server `ca` or `ca` chain (starting with the leaf certificate)
- 'cert` - the client certificate
- `key` - the client certifcate key
The data should be in PEM format.

In our examples, we have two configuration: `selfsign` with a `ca` entry and `selfsignedmtls` with all three entries.

TLS 1.2 and 1.3 are supported. TLS 1.0 and 1.1 are not supported.

## Try it out!

`rest_self` in tls.graphql provides an example of self signed certificates by pointing to the `selfsign` resource in config.yaml. That configuration contains `ca: STEPZEN_SERVER_CRT`
During `stepzen deploy`, the `STEPZEN_SERVER_CRT` environment variable is expanded and the result will be a yaml that looks like:
```
configurationset:
- configuration:
name: selfsign
ca: |
-----BEGIN CERTIFICATE-----
MIIF5zCCA8+gAwIBAgIUS2BwtghuA7PREQ5AWzOeeT+tCe4wDQYJKoZIhvcNAQEL
...
-----END CERTIFICATE-----
```

The `selfsignedmtls` configuration contains an example mutual TLS configuration.

Two safe approaches are to set the environment variables from secrets or to have a `.env` file.

See tricks below for some possible hurdles.


### Running a test

Testing mTLS or self-signed certificates locally is best done using local API Connect for GraphQL.
In the following, we'll generate the certificates using openssl, use openssl to for trivialself-signed cert servers
and use the stepzen cli local service mode as a client.

#### Steps
```
stepzen service start
stepzen login --config ~/.stepzen/stepzen-config.local.yaml
(cd tests; make env)
stepzen deploy

# start trivial local TLS server using openssl
# enable DEBUG if there are issues
((cd tests; make run_validation_server_self_sign) &
# wait until it gets establish 1-30s

# run the actual tests
stepzen request -f operations.graphql

# cleanup
stepzen service stop
# restore your SaaS or other credentials
```


### Tricks

You can set `STEPZEN_*` env variables in .env or using export.

For example:
```
export STEPZEN_SERVER_CRT=`cat server.pem`
```
will set STEPZEN_SERVER_CRT to something like this:
```
-----BEGIN CERTIFICATE-----\nMIIF5zCCA8+gAwIBAgIUS2BwtghuA7PREQ5AWzOeeT+tCe4wDQYJKoZIhvcNAQEL\nBQA....\nEUhqWbTk+y13A1OPfWbJu82zTKfJFvCAUgCf -----END CERTIFICATE-----"
```

Be aware that the `\n` will show up as spaces if you do echo $STEPZEN_SERVER_CRT

To double check, you'll want to do something like this:
```
cat <<EOF
$STEPZEN_SERVER_CRT
EOF
```
or use techniques outlined in Misconfigured config.yaml below.

You can also set the env variables in `.env`. In that case, you can either replace the line breaks with `\n` or simply quote them:
```
STEPZEN_SERVER_CRT="-----BEGIN CERTIFICATE-----\nMIIF5zCCA8+gAwIBAgIUS2BwtghuA7PREQ5AWzOeeT+tCe4wDQYJKoZIhvcNAQEL\n...\n-----END CERTIFICATE-----"
```
or
```
STEPZEN_SERVER_CRT="-----BEGIN CERTIFICATE-----
MIIF5zCCA8+gAwIBAgIUS2BwtghuA7PREQ5AWzOeeT+tCe4wDQYJKoZIhvcNAQEL
...
-----END CERTIFICATE-----"
```


### Misconfigured config.yaml

You can check if the config.yaml being uploaded by running
```
DEBUG="stepzen:sdk:trace" stepzen deploy
```
and looking for the configuration. Useful if the .env or exported variables are not
as expected in the config.yaml or if you've directly placed the data in the `config.yaml`
that it is as you expected.

You are looking for something that looks like:
```
"configuration":{"configurationset":[{"configuration":{"name":"selfsign","ca":
"-----BEGIN CERTIFICATE-----\nMIIF5zCCA8+gAwIBAgIUS2BwtghuA7PREQ5AWzOeeT+tCe4wDQYJKoZIhvcNAQEL\nBQAwbTE...
```
notice the '\n'. If you see spaces you've done something wrong upsteram.


### Debugging
You can debug using stepzen request by adding `-H "stepzen-debug-level: 1"`
```
stepzen request '{rest_self}' -H "stepzen-debug-level: 1"
```
and you can check the output for issues. You can check if tls values are being passed by looking for the tls
block. In this case for mutual tls, we'll have:
```
"tls": {
"clientCertificate": {
"keyPresent": true,
"present": true
},
"serverRootCA": {
"present": true
}
},
```
9 changes: 9 additions & 0 deletions rest/tls/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
configurationset:
- configuration:
name: selfsign
ca: STEPZEN_SERVER_CRT
- configuration:
name: selfsignedmtls
ca: STEPZEN_SERVER_CRT
cert: STEPZEN_CLIENT_CRT
key: STEPZEN_CLIENT_KEY
4 changes: 4 additions & 0 deletions rest/tls/index.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
schema @sdl(files: ["tls.graphql"]) {
query: Query
}

3 changes: 3 additions & 0 deletions rest/tls/operations.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
query run {
rest_self
}
3 changes: 3 additions & 0 deletions rest/tls/stepzen.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"endpoint": "api/tls"
}
32 changes: 32 additions & 0 deletions rest/tls/tests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Makefile to build and validate a pair of *example* self-signed certificates for *simple* tests

# enable to debug ssl server
# DEBUG:=-debug
all: client.crt server.crt env

# server.crt client.key server.key
client.crt:
openssl req -x509 -newkey rsa:4096 -keyout client.key -out client.crt -sha256 -days 7650 \
-subj "/C=US/ST=Florida/L=Jacksonville/O=LOCALCLIENT/OU=Com/CN=localhost" -nodes \
-addext "subjectAltName = DNS:localhost, DNS:myalt, DNS:host.docker.internal"

server.crt:
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -sha256 -days 7650 \
-subj "/C=US/ST=California/L=San Jose/O=LOCALSERVER/OU=Com/CN=localhost" -nodes \
-addext "subjectAltName = DNS:localhost, DNS:host.docker.internal"

run_validation_client_self_sign: client.crt
curl --cacert server.crt https://localhost:9443 -debug

run_validation_server_self_sign: server.crt
openssl s_server -accept 8443 -cert server.crt -key server.key $(DEBUG) -www

clean:
rm -f server.crt server.key client.crt client.key

env: ../.env

../.env: client.crt server.crt
( echo STEPZEN_CLIENT_CRT=\""`cat client.crt`"\"; \
echo STEPZEN_CLIENT_KEY=\""`cat client.key`"\"; \
echo STEPZEN_SERVER_CRT=\""`cat server.crt`"\") > ../.env
15 changes: 15 additions & 0 deletions rest/tls/tests/Test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const fs = require("fs");
const path = require("node:path");
const {
deployAndRun,
stepzen,
getTestDescription,
} = require("../../../tests/gqltest.js");

testDescription = getTestDescription("snippets", __dirname);

describe(testDescription, function () {
// empty tests since this test is not valid for SaaS
const tests = [];
return deployAndRun(__dirname, tests, stepzen.admin);
});
14 changes: 14 additions & 0 deletions rest/tls/tls.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type Query {
"""
will contact localhost using host.docker.internal and 8443 and selfsign configuration
the ecmascript is used to repackage any content coming back (openssl s_server returns html)
"""
rest_self: JSON
@rest(
endpoint: "https://host.docker.internal:8443/"
tls: "selfsign"
ecmascript: """
function transformREST(s) { return JSON.stringify({data100: s.length>100, accept_8443: s.includes("-accept 8443")})}
"""
)
}