Skip to content

Commit 490e8b8

Browse files
authored
[Feature] Improve JWT rotation (#587)
1 parent b7114fa commit 490e8b8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2560
-470
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Improve Helm 3 support
99
- Allow to customize ID Pod selectors
1010
- Add Label and Envs Pod customization
11+
- Improved JWT Rotation
1112

1213
## [1.0.3](https://github.com/arangodb/kube-arangodb/tree/1.0.3) (2020-05-25)
1314
- Prevent deletion of not known PVC's

lifecycle.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ func init() {
6262
cmdMain.AddCommand(cmdLifecycle)
6363
cmdLifecycle.AddCommand(cmdLifecyclePreStop)
6464
cmdLifecycle.AddCommand(cmdLifecycleCopy)
65+
cmdLifecycle.AddCommand(cmdLifecycleProbe)
6566

6667
cmdLifecycleCopy.Flags().StringVar(&lifecycleCopyOptions.TargetDir, "target", "", "Target directory to copy the executable to")
6768
}

lifecycle_probes.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2020 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
// Author Adam Janikowski
21+
//
22+
23+
package main
24+
25+
import (
26+
"crypto/tls"
27+
"fmt"
28+
"io/ioutil"
29+
"net/http"
30+
"os"
31+
"path"
32+
33+
"github.com/arangodb/go-driver/jwt"
34+
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
35+
"github.com/arangodb/kube-arangodb/pkg/util/constants"
36+
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
37+
"github.com/pkg/errors"
38+
"github.com/rs/zerolog/log"
39+
"github.com/spf13/cobra"
40+
)
41+
42+
var (
43+
cmdLifecycleProbe = &cobra.Command{
44+
Use: "probe",
45+
Run: cmdLifecycleProbeCheck,
46+
}
47+
48+
probeInput struct {
49+
SSL bool
50+
Auth bool
51+
Endpoint string
52+
JWTPath string
53+
}
54+
)
55+
56+
func init() {
57+
f := cmdLifecycleProbe.PersistentFlags()
58+
59+
f.BoolVarP(&probeInput.SSL, "ssl", "", false, "Determines if SSL is enabled")
60+
f.BoolVarP(&probeInput.Auth, "auth", "", false, "Determines if authentication is enabled")
61+
f.StringVarP(&probeInput.Endpoint, "endpoint", "", "/_api/version", "Determines if SSL is enabled")
62+
f.StringVarP(&probeInput.JWTPath, "jwt", "", k8sutil.ClusterJWTSecretVolumeMountDir, "Path to the JWT tokens")
63+
}
64+
65+
func probeClient() *http.Client {
66+
tr := &http.Transport{}
67+
68+
if probeInput.SSL {
69+
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
70+
}
71+
72+
client := &http.Client{
73+
Transport: tr,
74+
}
75+
76+
return client
77+
}
78+
79+
func probeEndpoint(endpoint string) string {
80+
proto := "http"
81+
if probeInput.SSL {
82+
proto = "https"
83+
}
84+
85+
return fmt.Sprintf("%s://%s:%d%s", proto, "127.0.0.1", k8sutil.ArangoPort, endpoint)
86+
}
87+
88+
func readJWTFile(file string) ([]byte, error) {
89+
p := path.Join(probeInput.JWTPath, file)
90+
log.Info().Str("path", p).Msgf("Try to use file")
91+
92+
f, err := os.Open(p)
93+
if err != nil {
94+
return nil, err
95+
}
96+
97+
defer f.Close()
98+
data, err := ioutil.ReadAll(f)
99+
if err != nil {
100+
return nil, err
101+
}
102+
103+
return data, nil
104+
}
105+
106+
func getJWTToken() ([]byte, error) {
107+
// Try read default one
108+
if token, err := readJWTFile(constants.SecretKeyToken); err == nil {
109+
log.Info().Str("token", constants.SecretKeyToken).Msgf("Using JWT Token")
110+
return token, nil
111+
}
112+
113+
// Try read active one
114+
if token, err := readJWTFile(pod.ActiveJWTKey); err == nil {
115+
log.Info().Str("token", pod.ActiveJWTKey).Msgf("Using JWT Token")
116+
return token, nil
117+
}
118+
119+
if files, err := ioutil.ReadDir(probeInput.JWTPath); err == nil {
120+
for _, file := range files {
121+
if token, err := readJWTFile(file.Name()); err == nil {
122+
log.Info().Str("token", file.Name()).Msgf("Using JWT Token")
123+
return token, nil
124+
}
125+
}
126+
}
127+
128+
return nil, errors.Errorf("Unable to find any token")
129+
}
130+
131+
func addAuthHeader(req *http.Request) error {
132+
if !probeInput.Auth {
133+
return nil
134+
}
135+
136+
token, err := getJWTToken()
137+
if err != nil {
138+
return err
139+
}
140+
141+
header, err := jwt.CreateArangodJwtAuthorizationHeader(string(token), "probe")
142+
if err != nil {
143+
return err
144+
}
145+
146+
req.Header.Add("Authorization", header)
147+
return nil
148+
}
149+
150+
func doRequest() (*http.Response, error) {
151+
client := probeClient()
152+
153+
req, err := http.NewRequest(http.MethodGet, probeEndpoint(probeInput.Endpoint), nil)
154+
if err != nil {
155+
return nil, err
156+
}
157+
158+
if err := addAuthHeader(req); err != nil {
159+
return nil, err
160+
}
161+
162+
return client.Do(req)
163+
}
164+
165+
func cmdLifecycleProbeCheck(cmd *cobra.Command, args []string) {
166+
if err := cmdLifecycleProbeCheckE(); err != nil {
167+
log.Error().Err(err).Msgf("Fatal")
168+
os.Exit(1)
169+
}
170+
}
171+
172+
func cmdLifecycleProbeCheckE() error {
173+
resp, err := doRequest()
174+
if err != nil {
175+
return err
176+
}
177+
178+
if resp.StatusCode != http.StatusOK {
179+
if resp.Body != nil {
180+
defer resp.Body.Close()
181+
if data, err := ioutil.ReadAll(resp.Body); err == nil {
182+
return errors.Errorf("Unexpected code: %d - %s", resp.StatusCode, string(data))
183+
}
184+
}
185+
186+
return errors.Errorf("Unexpected code: %d", resp.StatusCode)
187+
}
188+
189+
log.Info().Msgf("Check passed")
190+
191+
return nil
192+
}

pkg/apis/deployment/v1/hashes.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,26 @@
2222

2323
package v1
2424

25+
import shared "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1"
26+
2527
type DeploymentStatusHashes struct {
26-
Encryption DeploymentStatusHashList `json:"encryption,omitempty"`
27-
TLS DeploymentStatusHashesTLS `json:"tls,omitempty"`
28+
Encryption DeploymentStatusHashesEncryption `json:"rocksDBEncryption,omitempty"`
29+
TLS DeploymentStatusHashesTLS `json:"tls,omitempty"`
30+
JWT DeploymentStatusHashesJWT `json:"jwt,omitempty"`
31+
}
32+
33+
type DeploymentStatusHashesEncryption struct {
34+
Keys shared.HashList `json:"keys,omitempty"`
2835
}
2936

3037
type DeploymentStatusHashesTLS struct {
31-
CA *string `json:"ca,omitempty"`
32-
Truststore DeploymentStatusHashList `json:"truststore,omitempty"`
38+
CA *string `json:"ca,omitempty"`
39+
Truststore shared.HashList `json:"truststore,omitempty"`
40+
}
41+
42+
type DeploymentStatusHashesJWT struct {
43+
Active string `json:"active,omitempty"`
44+
Passive shared.HashList `json:"passive,omitempty"`
45+
46+
Propagated bool `json:"propagated,omitempty"`
3347
}

pkg/apis/deployment/v1/plan.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,18 @@ const (
101101
ActionTypeEncryptionKeyRefresh ActionType = "EncryptionKeyRefresh"
102102
// ActionTypeEncryptionKeyStatusUpdate update status object with current encryption keys
103103
ActionTypeEncryptionKeyStatusUpdate ActionType = "EncryptionKeyStatusUpdate"
104+
// ActionTypeJWTStatusUpdate update status of JWT Secret
105+
ActionTypeJWTStatusUpdate ActionType = "JWTStatusUpdate"
106+
// ActionTypeJWTSetActive change active JWT key
107+
ActionTypeJWTSetActive ActionType = "JWTSetActive"
108+
// ActionTypeJWTAdd add new JWT key
109+
ActionTypeJWTAdd ActionType = "JWTAdd"
110+
// ActionTypeJWTClean Clean old JWT key
111+
ActionTypeJWTClean ActionType = "JWTClean"
112+
// ActionTypeJWTRefresh refresh jwt tokens
113+
ActionTypeJWTRefresh ActionType = "JWTRefresh"
114+
// ActionTypeJWTPropagated change propagated flag
115+
ActionTypeJWTPropagated ActionType = "JWTPropagated"
104116
)
105117

106118
const (

pkg/apis/deployment/v1/zz_generated.deepcopy.go

Lines changed: 38 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)