go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/appengine/impl/machinetoken/machinetoken.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package machinetoken implements generation of LUCI machine tokens. 16 package machinetoken 17 18 import ( 19 "context" 20 "crypto/x509" 21 "encoding/base64" 22 "fmt" 23 "strings" 24 "time" 25 26 "google.golang.org/protobuf/proto" 27 28 "go.chromium.org/luci/common/clock" 29 "go.chromium.org/luci/common/retry/transient" 30 "go.chromium.org/luci/server/auth/signing" 31 32 tokenserver "go.chromium.org/luci/tokenserver/api" 33 "go.chromium.org/luci/tokenserver/api/admin/v1" 34 "go.chromium.org/luci/tokenserver/appengine/impl/utils/tokensigning" 35 ) 36 37 // tokenSigningContext is used to make sure machine token is not misused in 38 // place of some other token. 39 // 40 // See SigningContext in utils/tokensigning.Signer. 41 // 42 // TODO(vadimsh): Enable it. Requires some temporary hacks to accept old and 43 // new tokens at the same time. 44 const tokenSigningContext = "" 45 46 // MintParams is passed to Mint. 47 // 48 // The name of the machine (to put into the machine token) is extracted from 49 // the certificate. 50 type MintParams struct { 51 // Cert is the certificate used when authenticating the token requester. 52 // 53 // It's serial number will be put in the token. 54 Cert *x509.Certificate 55 56 // Config is a chunk of configuration related to the machine domain. 57 // 58 // It describes parameters for the token. Fetched from luci-config as part of 59 // CA configuration. 60 Config *admin.CertificateAuthorityConfig 61 62 // Signer produces RSA-SHA256 signatures using a token server key. 63 // 64 // Usually it is using SignBytes GAE API. 65 Signer signing.Signer 66 } 67 68 // MachineFQDN extracts the machine's FQDN from the certificate. 69 // 70 // It uses either Common Name or X509v3 Subject Alternative Name DNS field 71 // (depending on the presence of the later). If SAN DNS field is present, it 72 // will be used, should it be consistent with the Common Name. 73 // 74 // The extracted FQDN is convert to lower case. 75 // 76 // Returns an error if CN field is empty. 77 func (p *MintParams) MachineFQDN() (string, error) { 78 cn := strings.ToLower(p.Cert.Subject.CommonName) 79 if cn == "" { 80 return "", fmt.Errorf("unsupported cert, Subject CN field is required") 81 } 82 83 for _, altName := range p.Cert.DNSNames { 84 if dns := strings.ToLower(altName); strings.HasPrefix(dns, cn+".") { 85 return dns, nil 86 } 87 } 88 return cn, nil 89 } 90 91 // Validate checks that token minting parameters are allowed. 92 func (p *MintParams) Validate() error { 93 // Check FDQN. 94 fqdn, err := p.MachineFQDN() 95 if err != nil { 96 return err 97 } 98 chunks := strings.SplitN(fqdn, ".", 2) 99 if len(chunks) != 2 { 100 return fmt.Errorf("not a valid FQDN %q", fqdn) 101 } 102 domain := chunks[1] // e.g. "us-central1-a.c.project-id.internal" 103 104 // Check DomainConfig for given domain. 105 domainCfg := domainConfig(p.Config, domain) 106 if domainCfg == nil { 107 return fmt.Errorf("the domain %q is not listed in the config", domain) 108 } 109 if domainCfg.MachineTokenLifetime <= 0 { 110 return fmt.Errorf("machine tokens for machines in domain %q are not allowed", domain) 111 } 112 113 // We don't support negative SNs. 114 sn := p.Cert.SerialNumber 115 if sn.Sign() <= 0 { 116 return fmt.Errorf("invalid certificate serial number: %s", sn) 117 } 118 return nil 119 } 120 121 // domainConfig returns DomainConfig (part of *.cfg file) for a given domain. 122 // 123 // It enumerates all domains specified in the config finding first domain that 124 // is equal to 'domain' or has it as a subdomain. 125 // 126 // Returns nil if requested domain is not represented in the config. 127 func domainConfig(cfg *admin.CertificateAuthorityConfig, domain string) *admin.DomainConfig { 128 for _, domainCfg := range cfg.KnownDomains { 129 for _, domainInCfg := range domainCfg.Domain { 130 if domainInCfg == domain || strings.HasSuffix(domain, "."+domainInCfg) { 131 return domainCfg 132 } 133 } 134 } 135 return nil 136 } 137 138 // Mint generates a new machine token proto, signs and serializes it. 139 // 140 // Returns its body as a proto, and as a signed base64-encoded final token. 141 func Mint(c context.Context, params *MintParams) (*tokenserver.MachineTokenBody, string, error) { 142 if err := params.Validate(); err != nil { 143 return nil, "", err 144 } 145 fqdn, err := params.MachineFQDN() 146 if err != nil { 147 panic("impossible") // checked in Validate already 148 } 149 chunks := strings.SplitN(fqdn, ".", 2) 150 if len(chunks) != 2 { 151 panic("impossible") // checked in Validate already 152 } 153 cfg := domainConfig(params.Config, chunks[1]) 154 if cfg == nil { 155 panic("impossible") // checked in Validate already 156 } 157 158 srvInfo, err := params.Signer.ServiceInfo(c) 159 if err != nil { 160 return nil, "", transient.Tag.Apply(err) 161 } 162 163 body := &tokenserver.MachineTokenBody{ 164 MachineFqdn: fqdn, 165 IssuedBy: srvInfo.ServiceAccountName, 166 IssuedAt: uint64(clock.Now(c).Unix()), 167 Lifetime: uint64(cfg.MachineTokenLifetime), 168 CaId: params.Config.UniqueId, 169 CertSn: params.Cert.SerialNumber.Bytes(), 170 } 171 signed, err := SignToken(c, params.Signer, body) 172 if err != nil { 173 return nil, "", err 174 } 175 return body, signed, nil 176 } 177 178 // SignToken signs and serializes the machine subtoken. 179 // 180 // It doesn't do any validation. Assumes the prepared subtoken is valid. 181 // 182 // Produces base64-encoded token or a transient error. 183 func SignToken(c context.Context, signer signing.Signer, body *tokenserver.MachineTokenBody) (string, error) { 184 s := tokensigning.Signer{ 185 Signer: signer, 186 SigningContext: tokenSigningContext, 187 Encoding: base64.RawStdEncoding, 188 Wrap: func(w *tokensigning.Unwrapped) proto.Message { 189 return &tokenserver.MachineTokenEnvelope{ 190 TokenBody: w.Body, 191 RsaSha256: w.RsaSHA256Sig, 192 KeyId: w.KeyID, 193 } 194 }, 195 } 196 return s.SignToken(c, body) 197 } 198 199 // InspectToken returns information about the machine token. 200 // 201 // Inspection.Envelope is either nil or *tokenserver.MachineTokenEnvelope. 202 // Inspection.Body is either nil or *tokenserver.MachineTokenBody. 203 func InspectToken(c context.Context, certs tokensigning.CertificatesSupplier, tok string) (*tokensigning.Inspection, error) { 204 i := tokensigning.Inspector{ 205 Encoding: base64.RawStdEncoding, 206 Certificates: certs, 207 SigningContext: tokenSigningContext, 208 Envelope: func() proto.Message { return &tokenserver.MachineTokenEnvelope{} }, 209 Body: func() proto.Message { return &tokenserver.MachineTokenBody{} }, 210 Unwrap: func(e proto.Message) tokensigning.Unwrapped { 211 env := e.(*tokenserver.MachineTokenEnvelope) 212 return tokensigning.Unwrapped{ 213 Body: env.TokenBody, 214 RsaSHA256Sig: env.RsaSha256, 215 KeyID: env.KeyId, 216 } 217 }, 218 Lifespan: func(b proto.Message) tokensigning.Lifespan { 219 body := b.(*tokenserver.MachineTokenBody) 220 return tokensigning.Lifespan{ 221 NotBefore: time.Unix(int64(body.IssuedAt), 0), 222 NotAfter: time.Unix(int64(body.IssuedAt)+int64(body.Lifetime), 0), 223 } 224 }, 225 } 226 return i.InspectToken(c, tok) 227 }