1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package controlplane 18 19 import ( 20 "fmt" 21 "os" 22 "path/filepath" 23 24 "k8s.io/client-go/rest" 25 "sigs.k8s.io/controller-runtime/pkg/internal/testing/certs" 26 "sigs.k8s.io/controller-runtime/pkg/internal/testing/process" 27 ) 28 29 // User represents a Kubernetes user. 30 type User struct { 31 // Name is the user's Name. 32 Name string 33 // Groups are the groups to which the user belongs. 34 Groups []string 35 } 36 37 // Authn knows how to configure an API server for a particular type of authentication, 38 // and provision users under that authentication scheme. 39 // 40 // The methods must be called in the following order (as presented below in the interface 41 // for a mnemonic): 42 // 43 // 1. Configure 44 // 2. Start 45 // 3. AddUsers (0+ calls) 46 // 4. Stop. 47 type Authn interface { 48 // Configure provides the working directory to this authenticator, 49 // and configures the given API server arguments to make use of this authenticator. 50 // 51 // Should be called first. 52 Configure(workDir string, args *process.Arguments) error 53 // Start runs this authenticator. Will be called just before API server start. 54 // 55 // Must be called after Configure. 56 Start() error 57 // AddUser provisions a user, returning a copy of the given base rest.Config 58 // configured to authenticate as that users. 59 // 60 // May only be called while the authenticator is "running". 61 AddUser(user User, baseCfg *rest.Config) (*rest.Config, error) 62 // Stop shuts down this authenticator. 63 Stop() error 64 } 65 66 // CertAuthn is an authenticator (Authn) that makes use of client certificate authn. 67 type CertAuthn struct { 68 // ca is the CA used to sign the client certs 69 ca *certs.TinyCA 70 // certDir is the directory used to write the CA crt file 71 // so that the API server can read it. 72 certDir string 73 } 74 75 // NewCertAuthn creates a new client-cert-based Authn with a new CA. 76 func NewCertAuthn() (*CertAuthn, error) { 77 ca, err := certs.NewTinyCA() 78 if err != nil { 79 return nil, fmt.Errorf("unable to provision client certificate auth CA: %w", err) 80 } 81 return &CertAuthn{ 82 ca: ca, 83 }, nil 84 } 85 86 // AddUser provisions a new user that's authenticated via certificates, with 87 // the given uesrname and groups embedded in the certificate as expected by the 88 // API server. 89 func (c *CertAuthn) AddUser(user User, baseCfg *rest.Config) (*rest.Config, error) { 90 certs, err := c.ca.NewClientCert(certs.ClientInfo{ 91 Name: user.Name, 92 Groups: user.Groups, 93 }) 94 if err != nil { 95 return nil, fmt.Errorf("unable to create client certificates for %s: %w", user.Name, err) 96 } 97 98 crt, key, err := certs.AsBytes() 99 if err != nil { 100 return nil, fmt.Errorf("unable to serialize client certificates for %s: %w", user.Name, err) 101 } 102 103 cfg := rest.CopyConfig(baseCfg) 104 cfg.CertData = crt 105 cfg.KeyData = key 106 107 return cfg, nil 108 } 109 110 // caCrtPath returns the path to the on-disk client-cert CA crt file. 111 func (c *CertAuthn) caCrtPath() string { 112 return filepath.Join(c.certDir, "client-cert-auth-ca.crt") 113 } 114 115 // Configure provides the working directory to this authenticator, 116 // and configures the given API server arguments to make use of this authenticator. 117 func (c *CertAuthn) Configure(workDir string, args *process.Arguments) error { 118 c.certDir = workDir 119 args.Set("client-ca-file", c.caCrtPath()) 120 return nil 121 } 122 123 // Start runs this authenticator. Will be called just before API server start. 124 // 125 // Must be called after Configure. 126 func (c *CertAuthn) Start() error { 127 if len(c.certDir) == 0 { 128 return fmt.Errorf("start called before configure") 129 } 130 caCrt := c.ca.CA.CertBytes() 131 if err := os.WriteFile(c.caCrtPath(), caCrt, 0640); err != nil { //nolint:gosec 132 return fmt.Errorf("unable to save the client certificate CA to %s: %w", c.caCrtPath(), err) 133 } 134 135 return nil 136 } 137 138 // Stop shuts down this authenticator. 139 func (c *CertAuthn) Stop() error { 140 // no-op -- our workdir is cleaned up for us automatically 141 return nil 142 } 143