Skip to content

Commit

Permalink
[#579]: feature(h2c): optional certificates for TLS
Browse files Browse the repository at this point in the history
  • Loading branch information
rustatian authored Nov 19, 2024
2 parents 0c99c81 + 8a838fa commit fd14aaf
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 13 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@ jobs:
run: |
mkdir ./tests/coverage-ci
- name: Add custom CA certificate
run: |
cd tests
./env/temporal_tls/generate-test-certs.sh
export TEMPORAL_TLS_CERTS_DIR=/etc/temporal/config/certs
export TEMPORAL_LOCAL_CERT_DIR=$(pwd)/env/temporal_tls/certs
sudo cp $(pwd)/env/temporal_tls/certs/ca.cert /usr/local/share/ca-certificates/ca.crt
sudo update-ca-certificates
- name: Run Temporal canceller module tests
run: |
go test -timeout 20m -v -race -cover -tags=debug -failfast -coverpkg=$(cat ./tests/pkgs.txt) -coverprofile=./tests/coverage-ci/rrt_c.out -covermode=atomic canceller/canceller.go canceller/canceller_test.go
Expand Down
25 changes: 15 additions & 10 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type TLS struct {
RootCA string `mapstructure:"root_ca"`
AuthType ClientAuthType `mapstructure:"client_auth_type"`
ServerName string `mapstructure:"server_name"`
UseH2C bool `mapstructure:"use_h2c"`
// auth type
auth tls.ClientAuthType
}
Expand Down Expand Up @@ -138,20 +139,24 @@ func (c *Config) InitDefault() error {
}

if c.TLS != nil {
if _, err := os.Stat(c.TLS.Key); err != nil {
if os.IsNotExist(err) {
return errors.E(op, errors.Errorf("private key file '%s' does not exist", c.TLS.Key))
}
if c.TLS.Key != "" {
if _, err := os.Stat(c.TLS.Key); err != nil {
if os.IsNotExist(err) {
return errors.E(op, errors.Errorf("private key file '%s' does not exist", c.TLS.Key))
}

return errors.E(op, err)
return errors.E(op, err)
}
}

if _, err := os.Stat(c.TLS.Cert); err != nil {
if os.IsNotExist(err) {
return errors.E(op, errors.Errorf("public certificate file '%s' does not exist", c.TLS.Cert))
}
if c.TLS.Cert != "" {
if _, err := os.Stat(c.TLS.Cert); err != nil {
if os.IsNotExist(err) {
return errors.E(op, errors.Errorf("public certificate file '%s' does not exist", c.TLS.Cert))
}

return errors.E(op, err)
return errors.E(op, err)
}
}

// RootCA is optional, but if provided - check it
Expand Down
19 changes: 19 additions & 0 deletions tests/configs/.rr-h2c.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: '3'

rpc:
listen: tcp://127.0.0.1:6001

server:
command: "php ../php_test_files/worker.php"

temporal:
address: "localhost:4433"
cache_size: 10
activities:
num_workers: 1
tls:
use_h2c: true

logs:
mode: development
level: debug
12 changes: 10 additions & 2 deletions tests/env/docker-compose-temporal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,22 @@ services:
- "8125:8125/udp"
- "8126:8126"

envoy:
image: bitnami/envoy:latest
ports:
- "4433:443"
volumes:
- ./h2c.yaml:/opt/bitnami/envoy/conf/envoy.yaml
- ./temporal_tls/certs/client.pem:/etc/envoy/client.pem
- ./temporal_tls/certs/client.key:/etc/envoy/client.key
- ./temporal_tls/certs/ca.cert:/etc/envoy/ca.cert

postgresql:
container_name: temporal-postgresql
image: postgres:latest
environment:
POSTGRES_PASSWORD: temporal
POSTGRES_USER: temporal
ports:
- "5432:5432"

temporal-ui:
container_name: temporal-ui
Expand Down
63 changes: 63 additions & 0 deletions tests/env/h2c.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
static_resources:
listeners:
- name: listener_0
address:
socket_address:
address: 0.0.0.0
port_value: 443 # Public-facing TLS port
filter_chains:
- filter_chain_match: {}
filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: grpc_service
domains:
- "*" # Allows all domains to access
routes:
- match:
prefix: "/"
route:
cluster: grpc_backend
timeout: 0s
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
upgrade_configs:
- upgrade_type: h2c # Enables h2c upgrade for cleartext HTTP/2
transport_socket: # Move this out to align with filter_chains
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
alpn_protocols: "h2"
tls_certificates:
- certificate_chain:
filename: "/etc/envoy/client.pem"
private_key:
filename: "/etc/envoy/client.key"
validation_context:
trusted_ca:
filename: "/etc/envoy/ca.cert"

clusters:
- name: grpc_backend
connect_timeout: 5s
type: LOGICAL_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: grpc_backend
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: temporal # Temporal service hostname
port_value: 7233 # Temporal service port
http2_protocol_options: {} # Enables HTTP/2 for cleartext h2c communication
52 changes: 52 additions & 0 deletions tests/general/h2c_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package tests

import (
"context"
"sync"
"testing"
"time"

"tests/helpers"

"github.com/stretchr/testify/assert"
"go.temporal.io/sdk/client"
)

func Test_H2CWithoutCerts(t *testing.T) {
stopCh := make(chan struct{}, 1)
wg := &sync.WaitGroup{}
wg.Add(1)
s := helpers.NewTestServer(t, stopCh, wg, "../configs/.rr-h2c.yaml")

w, err := s.Client.ExecuteWorkflow(
context.Background(),
client.StartWorkflowOptions{
TaskQueue: "default",
},
"HistoryLengthWorkflow")
assert.NoError(t, err)

time.Sleep(time.Second)
var result any

ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
assert.NoError(t, w.Get(ctx, &result))

res := []float64{3, 8, 8, 15}
out := result.([]interface{})

for i := 0; i < len(res); i++ {
if res[i] != out[i].(float64) {
t.Fail()
}
}

we, err := s.Client.DescribeWorkflowExecution(context.Background(), w.GetID(), w.GetRunID())
assert.NoError(t, err)

assert.Equal(t, "Completed", we.WorkflowExecutionInfo.Status.String())
stopCh <- struct{}{}
wg.Wait()
time.Sleep(time.Second)
}
9 changes: 8 additions & 1 deletion tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,17 @@ func initTLS(cfg *Config) (*tls.Config, error) {
}, nil
}

if cfg.TLS.UseH2C {
return &tls.Config{
ServerName: cfg.TLS.ServerName,
MinVersion: tls.VersionTLS12,
}, nil
}

return &tls.Config{
ServerName: cfg.TLS.ServerName,
MinVersion: tls.VersionTLS12,
GetClientCertificate: getClientCertificate(cfg),
MinVersion: tls.VersionTLS12,
}, nil
}

Expand Down

0 comments on commit fd14aaf

Please sign in to comment.