mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-16 11:41:39 +00:00
cmd/containerboot: check that k8s secret permissions are correct.
Updates #6629. Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
parent
e79a1eb24a
commit
e36c27bcd1
@ -23,6 +23,54 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// checkSecretPermissions checks that the current pod has permission to read,
|
||||||
|
// write and patch secretName in the cluster.
|
||||||
|
func checkSecretPermissions(ctx context.Context, secretName string) error {
|
||||||
|
for _, verb := range []string{"get", "update", "patch"} {
|
||||||
|
sar := map[string]any{
|
||||||
|
"apiVersion": "authorization.k8s.io/v1",
|
||||||
|
"kind": "SelfSubjectAccessReview",
|
||||||
|
"spec": map[string]any{
|
||||||
|
"resourceAttributes": map[string]any{
|
||||||
|
"namespace": kubeNamespace,
|
||||||
|
"verb": verb,
|
||||||
|
"resource": "secrets",
|
||||||
|
"name": secretName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
bs, err := json.Marshal(sar)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews", bytes.NewReader(bs))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp, err := doKubeRequest(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
bs, err = io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var res struct {
|
||||||
|
Status struct {
|
||||||
|
Allowed bool `json:"allowed"`
|
||||||
|
} `json:"status"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(bs, &res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !res.Status.Allowed {
|
||||||
|
return fmt.Errorf("missing permission: cannot %s secret %q", verb, secretName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// findKeyInKubeSecret inspects the kube secret secretName for a data
|
// findKeyInKubeSecret inspects the kube secret secretName for a data
|
||||||
// field called "authkey", and returns its value if present.
|
// field called "authkey", and returns its value if present.
|
||||||
func findKeyInKubeSecret(ctx context.Context, secretName string) (string, error) {
|
func findKeyInKubeSecret(ctx context.Context, secretName string) (string, error) {
|
||||||
@ -193,8 +241,8 @@ func doKubeRequest(ctx context.Context, r *http.Request) (*http.Response, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||||
return resp, fmt.Errorf("got non-200 status code %d", resp.StatusCode)
|
return resp, fmt.Errorf("got non-200/201 status code %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
@ -116,16 +116,21 @@ func main() {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if cfg.InKubernetes && cfg.KubeSecret != "" && cfg.AuthKey == "" {
|
if cfg.InKubernetes && cfg.KubeSecret != "" {
|
||||||
key, err := findKeyInKubeSecret(ctx, cfg.KubeSecret)
|
if err := checkSecretPermissions(ctx, cfg.KubeSecret); err != nil {
|
||||||
if err != nil {
|
log.Fatalf("Some Kubernetes permissions are missing, please check your RBAC configuration: %v", err)
|
||||||
log.Fatalf("Getting authkey from kube secret: %v", err)
|
|
||||||
}
|
}
|
||||||
if key != "" {
|
if cfg.AuthKey == "" {
|
||||||
log.Print("Using authkey found in kube secret")
|
key, err := findKeyInKubeSecret(ctx, cfg.KubeSecret)
|
||||||
cfg.AuthKey = key
|
if err != nil {
|
||||||
} else {
|
log.Fatalf("Getting authkey from kube secret: %v", err)
|
||||||
log.Print("No authkey found in kube secret and TS_AUTHKEY not provided, login will be interactive if needed.")
|
}
|
||||||
|
if key != "" {
|
||||||
|
log.Print("Using authkey found in kube secret")
|
||||||
|
cfg.AuthKey = key
|
||||||
|
} else {
|
||||||
|
log.Print("No authkey found in kube secret and TS_AUTHKEY not provided, login will be interactive if needed.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -784,6 +784,12 @@ func (k *kubeServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
if r.Header.Get("Authorization") != "Bearer bearer_token" {
|
if r.Header.Get("Authorization") != "Bearer bearer_token" {
|
||||||
panic("client didn't provide bearer token in request")
|
panic("client didn't provide bearer token in request")
|
||||||
}
|
}
|
||||||
|
if r.URL.Path == "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews" {
|
||||||
|
// Just say yes to all SARs, we don't enforce RBAC.
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
fmt.Fprintln(w, `{"status":{"allowed":true}}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
if r.URL.Path != "/api/v1/namespaces/default/secrets/tailscale" {
|
if r.URL.Path != "/api/v1/namespaces/default/secrets/tailscale" {
|
||||||
panic(fmt.Sprintf("unhandled fake kube api path %q", r.URL.Path))
|
panic(fmt.Sprintf("unhandled fake kube api path %q", r.URL.Path))
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user