Skip to content

Commit d2cb378

Browse files
committed
Configure oauth-proxy with random cookie secret
The oauth-proxy uses the cookie secret as a key to sign and validate session cookies. This change generates a random 32 byte key and stores it in the skupper-console-session secret to be used by the proxy for this purpose. Signed-off-by: Christian Kruse <[email protected]> Improve error handling per review Signed-off-by: Christian Kruse <[email protected]>
1 parent 5503b3b commit d2cb378

File tree

6 files changed

+215
-5
lines changed

6 files changed

+215
-5
lines changed

api/types/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ const (
215215
SiteCaSecret string = "skupper-site-ca"
216216
ConsoleServerSecret string = "skupper-console-certs"
217217
ConsoleUsersSecret string = "skupper-console-users"
218+
ConsoleSessionSecret string = "skupper-console-session"
218219
PrometheusServerSecret string = "skupper-prometheus-certs"
219220
OauthRouterConsoleSecret string = "skupper-router-console-certs"
220221
ServiceCaSecret string = "skupper-service-ca"

client/router_create.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func OauthProxyContainer(serviceAccount string, servicePort string) *corev1.Cont
5252
"--upstream=http://localhost:" + servicePort,
5353
"--tls-cert=/etc/tls/proxy-certs/tls.crt",
5454
"--tls-key=/etc/tls/proxy-certs/tls.key",
55-
"--cookie-secret=SECRET",
55+
"--cookie-secret-file=/etc/skupper-console-session/session_secret",
5656
},
5757
Ports: []corev1.ContainerPort{
5858
{
@@ -335,6 +335,7 @@ func (cli *VanClient) GetVanControllerSpec(options types.SiteConfigSpec, van *ty
335335
envVars = append(envVars, corev1.EnvVar{Name: "FLOW_HOST", Value: "localhost"})
336336
mounts = append(mounts, []corev1.VolumeMount{})
337337
kube.AppendSecretVolume(&volumes, &mounts[oauthProxy], types.ConsoleServerSecret, "/etc/tls/proxy-certs/")
338+
kube.AppendSecretVolume(&volumes, &mounts[oauthProxy], types.ConsoleSessionSecret, "/etc/skupper-console-session/")
338339
} else if options.AuthMode == string(types.ConsoleAuthModeInternal) {
339340
envVars = append(envVars, corev1.EnvVar{Name: "FLOW_USERS", Value: "/etc/console-users"})
340341
kube.AppendSharedSecretVolume(&volumes, []*[]corev1.VolumeMount{&mounts[serviceController], &mounts[flowCollector]}, "skupper-console-users", "/etc/console-users/")
@@ -710,7 +711,7 @@ func configureDeployment(spec *types.DeploymentSpec, options *types.Tuning) erro
710711
}
711712
}
712713

713-
func (cli *VanClient) GetRouterSpecFromOpts(options types.SiteConfigSpec, siteId string) *types.RouterSpec {
714+
func (cli *VanClient) GetRouterSpecFromOpts(options types.SiteConfigSpec, siteId string) (*types.RouterSpec, error) {
714715
// skupper-router container index
715716
// TODO: update after dataplance changes
716717
const (
@@ -977,6 +978,15 @@ func (cli *VanClient) GetRouterSpecFromOpts(options types.SiteConfigSpec, siteId
977978
Labels: options.Labels,
978979
})
979980
}
981+
982+
if options.EnableFlowCollector && options.AuthMode == string(types.ConsoleAuthModeOpenshift) {
983+
sessionCreds, err := kube.GenerateConsoleSessionCredentials(nil)
984+
if err != nil {
985+
return van, fmt.Errorf("failed to generate console session credentials: %s", err)
986+
}
987+
credentials = append(credentials, sessionCreds)
988+
}
989+
980990
if options.AuthMode == string(types.ConsoleAuthModeInternal) && (options.EnableFlowCollector || options.EnableRestAPI) {
981991
userData := map[string][]byte{}
982992
if options.User != "" {
@@ -1162,7 +1172,7 @@ func (cli *VanClient) GetRouterSpecFromOpts(options types.SiteConfigSpec, siteId
11621172
}
11631173
van.Transport.Routes = routes
11641174

1165-
return van
1175+
return van, nil
11661176
}
11671177

11681178
func (cli *VanClient) GetRouterHostAliasesSpecFromTokens(ctx context.Context, namespace string) ([]corev1.HostAlias, error) {
@@ -1252,13 +1262,15 @@ func (cli *VanClient) RouterCreate(ctx context.Context, options types.SiteConfig
12521262
if siteId == "" {
12531263
siteId = utils.RandomId(10)
12541264
}
1255-
van := cli.GetRouterSpecFromOpts(options.Spec, siteId)
1265+
van, err := cli.GetRouterSpecFromOpts(options.Spec, siteId)
1266+
if err != nil {
1267+
return err
1268+
}
12561269
siteOwnerRef := asOwnerReference(options.Reference)
12571270
var ownerRefs []metav1.OwnerReference
12581271
if siteOwnerRef != nil {
12591272
ownerRefs = []metav1.OwnerReference{*siteOwnerRef}
12601273
}
1261-
var err error
12621274
hostAliases, err := cli.GetRouterHostAliasesSpecFromTokens(ctx, cli.GetNamespace())
12631275
if err != nil {
12641276
return err

client/router_create_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ func TestRouterCreateDefaults(t *testing.T) {
195195
types.LocalClientSecret,
196196
types.SiteServerSecret,
197197
types.ConsoleServerSecret,
198+
types.ConsoleSessionSecret,
198199
types.ServiceCaSecret,
199200
types.ServiceClientSecret},
200201
svcsExpected: []string{types.LocalTransportServiceName, types.TransportServiceName, types.ControllerServiceName, types.PrometheusServiceName},

client/router_update.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ func (cli *VanClient) RouterUpdateVersionInNamespace(ctx context.Context, hup bo
104104
addPrometheusServer := false
105105
moveClaims := false
106106
updateRole := false
107+
updateOauthProxy := false
107108
inprogress, originalVersion, err := cli.isUpdating(namespace)
108109
if err != nil {
109110
return false, err
@@ -121,6 +122,7 @@ func (cli *VanClient) RouterUpdateVersionInNamespace(ctx context.Context, hup bo
121122
moveClaims = !config.IsEdge()
122123
updateRole = true //config-sync requires extra permission to write skupper-network-status configmap
123124
}
125+
updateOauthProxy = utils.LessRecentThanVersion(originalVersion, "1.7.2")
124126
} else {
125127
originalVersion = site.Version
126128
}
@@ -148,6 +150,7 @@ func (cli *VanClient) RouterUpdateVersionInNamespace(ctx context.Context, hup bo
148150
moveClaims = !config.IsEdge()
149151
updateRole = true //config-sync requires extra permission to write skupper-network-status configmap
150152
}
153+
updateOauthProxy = utils.LessRecentThanVersion(originalVersion, "1.7.2")
151154

152155
err = cli.updateStarted(site.Version, namespace, configmap.ObjectMeta.OwnerReferences)
153156
if err != nil {
@@ -666,6 +669,25 @@ func (cli *VanClient) RouterUpdateVersionInNamespace(ctx context.Context, hup bo
666669
}
667670
}
668671

672+
if updateOauthProxy {
673+
siteConfig, _ := cli.SiteConfigInspectInNamespace(ctx, nil, namespace)
674+
if siteConfig.Spec.EnableFlowCollector &&
675+
siteConfig.Spec.EnableConsole &&
676+
siteConfig.Spec.AuthMode == string(types.ConsoleAuthModeOpenshift) {
677+
var owner *metav1.OwnerReference
678+
if len(configmap.ObjectMeta.OwnerReferences) > 0 {
679+
owner = &configmap.ObjectMeta.OwnerReferences[0]
680+
}
681+
changed, err := ensureOauthProxyConfig(cli, owner, controller)
682+
if err != nil {
683+
return false, err
684+
}
685+
if changed {
686+
updateController = true
687+
}
688+
}
689+
}
690+
669691
desiredControllerImage := images.GetServiceControllerImageName()
670692
if controller.Spec.Template.Spec.Containers[0].Image != desiredControllerImage {
671693
controller.Spec.Template.Spec.Containers[0].Image = desiredControllerImage
@@ -1473,3 +1495,78 @@ func createFlowCollectorSidecar(ctx context.Context, cli *VanClient, controller
14731495
controller.Spec.Template.Spec.Containers = append(controller.Spec.Template.Spec.Containers, flowContainer)
14741496
return nil
14751497
}
1498+
1499+
func ensureOauthProxyConfig(cli *VanClient, owner *metav1.OwnerReference, controller *appsv1.Deployment) (bool, error) {
1500+
var edited bool
1501+
// ensure the console session credentials are present
1502+
sessionCreds, err := kube.GenerateConsoleSessionCredentials(nil)
1503+
if err != nil {
1504+
return false, err
1505+
}
1506+
edited = true
1507+
_, err = kube.NewSecret(sessionCreds, owner, cli.Namespace, cli.KubeClient)
1508+
if err != nil {
1509+
if !errors.IsAlreadyExists(err) { // ignore already exists errors
1510+
return false, fmt.Errorf("error creating skupper-console-session secret: %s", err)
1511+
}
1512+
edited = false
1513+
}
1514+
1515+
// ensure oauth-proxy container spec is updated
1516+
idx := -1
1517+
for i, c := range controller.Spec.Template.Spec.Containers {
1518+
if c.Name == "oauth-proxy" {
1519+
idx = i
1520+
break
1521+
}
1522+
}
1523+
if idx < 0 {
1524+
return edited, fmt.Errorf("error updating oauth-proxy spec: container not found")
1525+
}
1526+
desiredCtr := OauthProxyContainer(types.ControllerServiceAccountName, fmt.Sprint(types.ConsoleOpenShiftServicePort))
1527+
actualCtr := controller.Spec.Template.Spec.Containers[idx]
1528+
if !reflect.DeepEqual(actualCtr.Args, desiredCtr.Args) {
1529+
edited = true
1530+
actualCtr.Args = desiredCtr.Args
1531+
}
1532+
1533+
// ensure console session secret is mounted as volume
1534+
volIdx := -1
1535+
for i, v := range controller.Spec.Template.Spec.Volumes {
1536+
if v.Name == types.ConsoleSessionSecret {
1537+
volIdx = i
1538+
break
1539+
}
1540+
}
1541+
if volIdx < 0 {
1542+
edited = true
1543+
controller.Spec.Template.Spec.Volumes = append(controller.Spec.Template.Spec.Volumes, corev1.Volume{
1544+
Name: types.ConsoleSessionSecret,
1545+
VolumeSource: corev1.VolumeSource{
1546+
Secret: &corev1.SecretVolumeSource{
1547+
SecretName: types.ConsoleSessionSecret,
1548+
},
1549+
},
1550+
})
1551+
}
1552+
1553+
mtIdx := -1
1554+
for i, m := range actualCtr.VolumeMounts {
1555+
if m.Name == types.ConsoleSessionSecret {
1556+
mtIdx = i
1557+
break
1558+
}
1559+
}
1560+
if mtIdx < 0 {
1561+
edited = true
1562+
actualCtr.VolumeMounts = append(actualCtr.VolumeMounts, corev1.VolumeMount{
1563+
Name: types.ConsoleSessionSecret,
1564+
MountPath: "/etc/skupper-console-session/",
1565+
})
1566+
}
1567+
1568+
if edited {
1569+
controller.Spec.Template.Spec.Containers[idx] = actualCtr
1570+
}
1571+
return edited, nil
1572+
}

pkg/kube/secrets.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package kube
22

33
import (
44
"context"
5+
"crypto/rand"
6+
"encoding/base64"
57
"fmt"
8+
"io"
69
"strings"
710

811
corev1 "k8s.io/api/core/v1"
@@ -161,3 +164,28 @@ func RegenerateCredentials(credential types.Credential, namespace string, ca *co
161164
current.Data = regenerated.Data
162165
return cli.CoreV1().Secrets(namespace).Update(context.TODO(), current, metav1.UpdateOptions{})
163166
}
167+
168+
func GenerateConsoleSessionCredentials(source io.Reader) (types.Credential, error) {
169+
var (
170+
key [32]byte // key is 32 random bytes
171+
keyText [44]byte // keyText is the base64 encoded key - 44 characters
172+
cred types.Credential
173+
)
174+
if source == nil {
175+
source = rand.Reader
176+
}
177+
if _, err := io.ReadFull(source, key[:]); err != nil {
178+
return cred, fmt.Errorf("error generating console session key: %s", err)
179+
}
180+
base64.URLEncoding.Encode(keyText[:], key[:])
181+
return types.Credential{
182+
CA: "",
183+
Name: types.ConsoleSessionSecret,
184+
Subject: "",
185+
ConnectJson: false,
186+
Data: map[string][]byte{
187+
"session_secret": keyText[:],
188+
},
189+
Post: false,
190+
}, nil
191+
}

pkg/kube/secrets_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package kube
2+
3+
import (
4+
"bytes"
5+
"crypto/rand"
6+
"fmt"
7+
"io"
8+
"testing"
9+
10+
"github.com/skupperproject/skupper/api/types"
11+
"gotest.tools/assert"
12+
)
13+
14+
func TestGenerateConsoleSessionCredentials(t *testing.T) {
15+
zeros := bytes.NewReader(make([]byte, 128))
16+
const zerosEncoded = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
17+
18+
exampleText := "GenerateConsoleSessionCredentials"
19+
exampleTextEncoded := "R2VuZXJhdGVDb25zb2xlU2Vzc2lvbkNyZWRlbnRpYWw="
20+
21+
testcases := []struct {
22+
Input io.Reader
23+
CheckData func(map[string][]byte) error
24+
}{
25+
{
26+
Input: zeros,
27+
CheckData: func(data map[string][]byte) error {
28+
if string(data["session_secret"]) != zerosEncoded {
29+
return fmt.Errorf("session secret should have been %q but got %v", zerosEncoded, data)
30+
}
31+
return nil
32+
},
33+
},
34+
{
35+
Input: nil,
36+
CheckData: func(data map[string][]byte) error {
37+
if len(data["session_secret"]) != 44 {
38+
return fmt.Errorf("session secret should have been 44 bytes long but got %v", data)
39+
}
40+
return nil
41+
},
42+
},
43+
{
44+
Input: rand.Reader,
45+
CheckData: func(data map[string][]byte) error {
46+
if len(data["session_secret"]) != 44 {
47+
return fmt.Errorf("session secret should have been 44 bytes long but got %v", data)
48+
}
49+
return nil
50+
},
51+
},
52+
{
53+
Input: bytes.NewReader([]byte(exampleText)),
54+
CheckData: func(data map[string][]byte) error {
55+
if string(data["session_secret"]) != exampleTextEncoded {
56+
return fmt.Errorf("session secret should have been %q but got %v", exampleTextEncoded, data)
57+
}
58+
return nil
59+
},
60+
},
61+
}
62+
63+
for _, tc := range testcases {
64+
t.Run("", func(t *testing.T) {
65+
creds, err := GenerateConsoleSessionCredentials(tc.Input)
66+
assert.Assert(t, err)
67+
assert.Equal(t, creds.Name, types.ConsoleSessionSecret)
68+
assert.Assert(t, tc.CheckData(creds.Data))
69+
})
70+
}
71+
}

0 commit comments

Comments
 (0)