cmd/containerboot: check that k8s secret permissions are correct.

Updates #6629.

Signed-off-by: David Anderson <danderson@tailscale.com>
pull/6649/head
David Anderson 2 years ago committed by Dave Anderson
parent e79a1eb24a
commit e36c27bcd1

@ -23,6 +23,54 @@ import (
"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
// field called "authkey", and returns its value if present.
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 {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return resp, fmt.Errorf("got non-200 status code %d", resp.StatusCode)
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
return resp, fmt.Errorf("got non-200/201 status code %d", resp.StatusCode)
}
return resp, nil
}

@ -116,7 +116,11 @@ func main() {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
if cfg.InKubernetes && cfg.KubeSecret != "" && cfg.AuthKey == "" {
if cfg.InKubernetes && cfg.KubeSecret != "" {
if err := checkSecretPermissions(ctx, cfg.KubeSecret); err != nil {
log.Fatalf("Some Kubernetes permissions are missing, please check your RBAC configuration: %v", err)
}
if cfg.AuthKey == "" {
key, err := findKeyInKubeSecret(ctx, cfg.KubeSecret)
if err != nil {
log.Fatalf("Getting authkey from kube secret: %v", err)
@ -128,6 +132,7 @@ func main() {
log.Print("No authkey found in kube secret and TS_AUTHKEY not provided, login will be interactive if needed.")
}
}
}
client, daemonPid, err := startTailscaled(ctx, cfg)
if err != nil {

@ -784,6 +784,12 @@ func (k *kubeServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer bearer_token" {
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" {
panic(fmt.Sprintf("unhandled fake kube api path %q", r.URL.Path))
}

Loading…
Cancel
Save