Skip to content

Commit

Permalink
feat: support TLS session resumption through session tickets (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
nettoclaudio authored Jun 5, 2020
1 parent a84e9cb commit 04a547d
Show file tree
Hide file tree
Showing 12 changed files with 1,170 additions and 15 deletions.
12 changes: 12 additions & 0 deletions deploy/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,15 @@ rules:
- pods/exec
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rpaas-session-tickets-rotator
rules:
- apiGroups: [""]
resources:
- secrets
verbs:
- get
- patch
14 changes: 14 additions & 0 deletions deploy/role_binding.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,17 @@ roleRef:
kind: ClusterRole
name: rpaas-cache-syncer
apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: rpaas-session-tickets-rotator
subjects:
- kind: ServiceAccount
name: rpaas-session-tickets-rotator
namespace: rpaas-operator-integration
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: rpaas-session-tickets-rotator
---
6 changes: 6 additions & 0 deletions deploy/service_account.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ apiVersion: v1
kind: ServiceAccount
metadata:
name: rpaas-cache-snapshot
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: rpaas-session-tickets-rotator
---
89 changes: 75 additions & 14 deletions internal/pkg/rpaas/nginx/configuration_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,21 +156,57 @@ func k8sQuantityToNginx(quantity *resource.Quantity) string {
return strconv.Itoa(int(bytesN))
}

func tlsSessionTicketEnabled(instance *v1alpha1.RpaasInstance) bool {
return instance != nil &&
instance.Spec.TLSSessionResumption != nil &&
instance.Spec.TLSSessionResumption.SessionTicket != nil
}

func tlsSessionTicketKeys(instance *v1alpha1.RpaasInstance) int {
if !tlsSessionTicketEnabled(instance) {
return 0
}

return int(instance.Spec.TLSSessionResumption.SessionTicket.KeepLastKeys) + 1
}

func tlsSessionTicketTimeout(instance *v1alpha1.RpaasInstance) int {
nkeys := tlsSessionTicketKeys(instance)

keyRotationInterval := v1alpha1.DefaultSessionTicketKeyRotationInteval
if tlsSessionTicketEnabled(instance) &&
instance.Spec.TLSSessionResumption.SessionTicket.KeyRotationInterval != uint32(0) {
keyRotationInterval = instance.Spec.TLSSessionResumption.SessionTicket.KeyRotationInterval
}

return nkeys * int(keyRotationInterval)
}

var templateFuncs = template.FuncMap(map[string]interface{}{
"boolValue": v1alpha1.BoolValue,
"buildLocationKey": buildLocationKey,
"hasRootPath": hasRootPath,
"toLower": strings.ToLower,
"toUpper": strings.ToUpper,
"managePort": managePort,
"httpPort": httpPort,
"httpsPort": httpsPort,
"purgeLocationMatch": purgeLocationMatch,
"vtsLocationMatch": vtsLocationMatch,
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix,
"k8sQuantityToNginx": k8sQuantityToNginx,
"boolValue": v1alpha1.BoolValue,
"buildLocationKey": buildLocationKey,
"hasRootPath": hasRootPath,
"toLower": strings.ToLower,
"toUpper": strings.ToUpper,
"managePort": managePort,
"httpPort": httpPort,
"httpsPort": httpsPort,
"purgeLocationMatch": purgeLocationMatch,
"vtsLocationMatch": vtsLocationMatch,
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix,
"k8sQuantityToNginx": k8sQuantityToNginx,
"tlsSessionTicketEnabled": tlsSessionTicketEnabled,
"tlsSessionTicketKeys": tlsSessionTicketKeys,
"tlsSessionTicketTimeout": tlsSessionTicketTimeout,
"iterate": func(n int) []int {
v := make([]int, n)
for i := 0; i < n; i++ {
v[i] = i
}
return v
},
})

var defaultMainTemplate = template.Must(template.New("main").
Expand Down Expand Up @@ -245,6 +281,21 @@ http {
{{- end }}
{{- end}}
{{- if tlsSessionTicketEnabled $instance }}
{{- with $instance.Spec.TLSSessionResumption.SessionTicket }}{{ "\n" }}
ssl_session_cache off;
ssl_session_tickets on;
{{- range $index, $_ := (iterate (tlsSessionTicketKeys $instance)) }}
ssl_session_ticket_key tickets/ticket.{{ $index }}.key;
{{- end }}
{{- with (tlsSessionTicketTimeout $instance) }}
ssl_session_timeout {{ . }}m;
{{- end }}
{{- end }}
{{- end }}
{{- range $index, $bind := $instance.Spec.Binds }}
{{- if eq $index 0 }}
Expand Down Expand Up @@ -283,6 +334,16 @@ http {
}
init_worker_by_lua_block {
{{- if tlsSessionTicketEnabled $instance }}
{{- with $instance.Spec.TLSSessionResumption.SessionTicket }}
local rpaasv2_session_ticket_reloader = require('tsuru.rpaasv2.tls.session_ticket_reloader'):new({
ticket_file = '/etc/nginx/tickets/ticket.0.key',
retain_last_keys = {{ tlsSessionTicketKeys $instance }},
})
rpaasv2_session_ticket_reloader:start_worker()
{{- end }}
{{- end }}
{{- template "lua-worker" . }}
}
Expand Down
60 changes: 60 additions & 0 deletions internal/pkg/rpaas/nginx/configuration_render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,66 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) {
assert.Regexp(t, `listen 20003;`, result)
},
},
{
name: "with TLS session tickets enabled (using default values)",
data: ConfigurationData{
Config: &v1alpha1.NginxConfig{},
Instance: &v1alpha1.RpaasInstance{
Spec: v1alpha1.RpaasInstanceSpec{
TLSSessionResumption: &v1alpha1.TLSSessionResumption{
SessionTicket: &v1alpha1.TLSSessionTicket{},
},
},
},
},
assertion: func(t *testing.T, result string) {
assert.Regexp(t, `ssl_session_cache\s+off;`, result)
assert.Regexp(t, `ssl_session_tickets\s+on;`, result)
assert.Regexp(t, `ssl_session_ticket_key\s+tickets/ticket.0.key;`, result)
assert.Regexp(t, `ssl_session_timeout\s+60m;`, result)
assert.Regexp(t, `init_worker_by_lua_block \{\n*
\s+local rpaasv2_session_ticket_reloader = require\('tsuru.rpaasv2.tls.session_ticket_reloader'\):new\(\{
\s+ticket_file = '/etc/nginx/tickets/ticket.0.key',
\s+retain_last_keys = 1,
\s+\}\)
\s+rpaasv2_session_ticket_reloader:start_worker\(\)
\s+\}`, result)
},
},
{
name: "with TLS session tickets enabled and custom values",
data: ConfigurationData{
Config: &v1alpha1.NginxConfig{},
Instance: &v1alpha1.RpaasInstance{
Spec: v1alpha1.RpaasInstanceSpec{
TLSSessionResumption: &v1alpha1.TLSSessionResumption{
SessionTicket: &v1alpha1.TLSSessionTicket{
KeepLastKeys: uint32(5),
KeyRotationInterval: uint32(60 * 24), // daily
},
},
},
},
},
assertion: func(t *testing.T, result string) {
assert.Regexp(t, `ssl_session_cache\s+off;`, result)
assert.Regexp(t, `ssl_session_tickets\s+on;`, result)
assert.Regexp(t, `ssl_session_ticket_key\s+tickets/ticket.0.key;`, result)
assert.Regexp(t, `ssl_session_ticket_key\s+tickets/ticket.1.key;`, result)
assert.Regexp(t, `ssl_session_ticket_key\s+tickets/ticket.2.key;`, result)
assert.Regexp(t, `ssl_session_ticket_key\s+tickets/ticket.3.key;`, result)
assert.Regexp(t, `ssl_session_ticket_key\s+tickets/ticket.4.key;`, result)
assert.Regexp(t, `ssl_session_ticket_key\s+tickets/ticket.5.key;`, result)
assert.Regexp(t, `ssl_session_timeout\s+8640m;`, result)
assert.Regexp(t, `init_worker_by_lua_block \{\n*
\s+local rpaasv2_session_ticket_reloader = require\('tsuru.rpaasv2.tls.session_ticket_reloader'\):new\(\{
\s+ticket_file = '/etc/nginx/tickets/ticket.0.key',
\s+retain_last_keys = 6,
\s+\}\)
\s+rpaasv2_session_ticket_reloader:start_worker\(\)
\s+\}`, result)
},
},
}

for _, tt := range tests {
Expand Down
78 changes: 78 additions & 0 deletions lualib/tsuru/rpaasv2/tls/session_ticket.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
-- Copyright 2020 tsuru authors. All rights reserved.
-- Use of this source code is governed by a BSD-style
-- license that can be found in the LICENSE file.
--

local _M = {}

local ffi = require('ffi')
local C = ffi.C
local ffi_str = ffi.string

local base = require('resty.core.base')
local table = require('table')

local get_string_buf = base.get_string_buf
local get_errmsg_ptr = base.get_errmsg_ptr
local void_ptr_type = ffi.typeof('void*')
local void_ptr_ptr_type = ffi.typeof('void**')
local ptr_size = ffi.sizeof(void_ptr_type)

ffi.cdef[[
int ngx_http_lua_ffi_get_ssl_ctx_count(void);

int ngx_http_lua_ffi_get_ssl_ctx_list(void **buf);

int ngx_http_lua_ffi_update_ticket_encryption_key(void *ctx, const unsigned char *key, unsigned int nkeys, const unsigned int key_length, char **err);
]]

local function get_ssl_contexts()
local n = C.ngx_http_lua_ffi_get_ssl_ctx_count()
if n < 0 then
return nil, 'ssl context cannot be negative'
end

if n == 0 then
return nil, nil
end

local buffer = ffi.cast(void_ptr_ptr_type, get_string_buf(ptr_size * n))
local rc = ffi.C.ngx_http_lua_ffi_get_ssl_ctx_list(buffer)
if rc ~= 0 then -- not NGX_OK
return nil, 'cannot get the ssl contexts'
end

local ctxs = table.new(n, 0)
for i = 1, n do
ctxs[i] = buffer[i - 1]
end

return ctxs, nil
end

function _M.update_ticket_encryption_key(key, nkeys)
local ssl_contexts, err = get_ssl_contexts()
if err then
return err
end

if not ssl_contexts or #ssl_contexts == 0 then
return 'no ssl ctx set'
end

if #key ~= 48 and #key ~= 80 then
return 'ssl ticket key must either have 48 or 80 bytes'
end

for _, ctx in ipairs(ssl_contexts) do
local errmsg = get_errmsg_ptr()
local rc = C.ngx_http_lua_ffi_update_ticket_encryption_key(ctx, key, nkeys, #key, errmsg)
if rc ~= 0 then -- not NGX_OK
return 'failed to update the key into OpenSSL context: ' .. ffi_str(errmsg[0])
end
end

return nil
end

return _M
Loading

0 comments on commit 04a547d

Please sign in to comment.