go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/appengine/impl/machinetoken/rpc_mint_machine_token.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 16 17 import ( 18 "context" 19 "crypto/x509" 20 "fmt" 21 "time" 22 23 "go.opentelemetry.io/otel/trace" 24 "google.golang.org/grpc/codes" 25 "google.golang.org/grpc/status" 26 "google.golang.org/protobuf/proto" 27 "google.golang.org/protobuf/types/known/timestamppb" 28 29 "go.chromium.org/luci/common/clock" 30 "go.chromium.org/luci/common/logging" 31 "go.chromium.org/luci/common/retry/transient" 32 "go.chromium.org/luci/server/auth" 33 "go.chromium.org/luci/server/auth/signing" 34 35 tokenserver "go.chromium.org/luci/tokenserver/api" 36 "go.chromium.org/luci/tokenserver/api/admin/v1" 37 "go.chromium.org/luci/tokenserver/api/minter/v1" 38 39 "go.chromium.org/luci/tokenserver/appengine/impl/certchecker" 40 "go.chromium.org/luci/tokenserver/appengine/impl/certconfig" 41 "go.chromium.org/luci/tokenserver/appengine/impl/utils" 42 ) 43 44 // MintMachineTokenRPC implements TokenMinter.MintMachineToken RPC method. 45 type MintMachineTokenRPC struct { 46 // Signer is mocked in tests. 47 // 48 // In prod it is the default server signer that uses server's service account. 49 Signer signing.Signer 50 51 // CheckCertificate is mocked in tests. 52 // 53 // In prod it is certchecker.CheckCertificate. 54 CheckCertificate func(c context.Context, cert *x509.Certificate) (*certconfig.CA, error) 55 56 // LogToken is mocked in tests. 57 // 58 // In prod it is produced by NewTokenLogger. 59 LogToken TokenLogger 60 } 61 62 // MintMachineToken generates a new token for an authenticated machine. 63 func (r *MintMachineTokenRPC) MintMachineToken(c context.Context, req *minter.MintMachineTokenRequest) (*minter.MintMachineTokenResponse, error) { 64 // Parse serialized portion of the request and do minimal validation before 65 // checking the signature to reject obviously bad requests. 66 if len(req.SerializedTokenRequest) == 0 { 67 return nil, status.Errorf(codes.InvalidArgument, "empty request") 68 } 69 tokenReq := minter.MachineTokenRequest{} 70 if err := proto.Unmarshal(req.SerializedTokenRequest, &tokenReq); err != nil { 71 return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal TokenRequest - %s", err) 72 } 73 74 switch tokenReq.TokenType { 75 case tokenserver.MachineTokenType_LUCI_MACHINE_TOKEN: 76 // supported 77 default: 78 return r.mintingErrorResponse( 79 c, minter.ErrorCode_UNSUPPORTED_TOKEN_TYPE, 80 "token_type %s is not supported", tokenReq.TokenType) 81 } 82 83 // Timestamp is required. 84 issuedAt := tokenReq.IssuedAt.AsTime() 85 if issuedAt.IsZero() { 86 return r.mintingErrorResponse(c, minter.ErrorCode_BAD_TIMESTAMP, "issued_at is required") 87 } 88 89 // It should be within acceptable range. 90 now := clock.Now(c) 91 notBefore := now.Add(-10 * time.Minute) 92 notAfter := now.Add(10 * time.Minute) 93 if issuedAt.Before(notBefore) || issuedAt.After(notAfter) { 94 return r.mintingErrorResponse( 95 c, minter.ErrorCode_BAD_TIMESTAMP, 96 "issued_at timestamp is not within acceptable range, check your clock") 97 } 98 99 // The certificate must be valid. 100 cert, err := x509.ParseCertificate(tokenReq.Certificate) 101 if err != nil { 102 return r.mintingErrorResponse( 103 c, minter.ErrorCode_BAD_CERTIFICATE_FORMAT, 104 "failed to parse the certificate (expecting x509 cert DER)") 105 } 106 107 // Check the signature before proceeding. Use switch when picking an algo 108 // as a reminder to add a new branch if new signature scheme is added. 109 var algo x509.SignatureAlgorithm 110 switch tokenReq.SignatureAlgorithm { 111 case minter.SignatureAlgorithm_SHA256_RSA_ALGO: 112 algo = x509.SHA256WithRSA 113 default: 114 return r.mintingErrorResponse( 115 c, minter.ErrorCode_UNSUPPORTED_SIGNATURE, 116 "signature_algorithm %s is not supported", tokenReq.SignatureAlgorithm) 117 } 118 err = cert.CheckSignature(algo, req.SerializedTokenRequest, req.Signature) 119 if err != nil { 120 return r.mintingErrorResponse( 121 c, minter.ErrorCode_BAD_SIGNATURE, 122 "signature verification failed - %s", err) 123 } 124 125 // At this point we know the request was signed by the holder of a private key 126 // that matches the certificate. 127 // 128 // Let's make sure the token server knows about that key, i.e. the certificate 129 // itself is signed by some trusted CA, it is valid (not expired), and it 130 // hasn't been revoked yet. CheckCertificate does these checks. 131 ca, err := r.CheckCertificate(c, cert) 132 133 // Recognize error codes related to CA cert checking. Everything else is 134 // transient errors. 135 if err != nil { 136 if certchecker.IsCertInvalidError(err) { 137 return r.mintingErrorResponse(c, minter.ErrorCode_UNTRUSTED_CERTIFICATE, "%s", err) 138 } 139 return nil, status.Errorf(codes.Internal, "failed to check the certificate - %s", err) 140 } 141 142 // At this point we trust what's in MachineTokenRequest, proceed with 143 // generating the token. 144 args := mintTokenArgs{ 145 Config: ca.ParsedConfig, 146 Cert: cert, 147 Request: &tokenReq, 148 } 149 switch tokenReq.TokenType { 150 case tokenserver.MachineTokenType_LUCI_MACHINE_TOKEN: 151 resp, body, err := r.mintLuciMachineToken(c, args) 152 switch { 153 case err != nil: // grpc-level error 154 return nil, err 155 case resp == nil: // should not happen 156 panic("both resp and err can't be nil") 157 case resp.ErrorCode != 0: // logic-level error 158 if resp.TokenResponse != nil { 159 panic("TokenResponse must be nil if ErrorCode != 0") 160 } 161 return resp, nil 162 } 163 if resp.TokenResponse == nil { 164 panic("TokenResponse must not be nil if ErrorCode == 0") 165 } 166 if r.LogToken != nil { 167 // Errors during logging are considered not fatal. We have a monitoring 168 // counter that tracks number of errors, so they are not totally 169 // invisible. 170 tokInfo := MintedTokenInfo{ 171 Request: &tokenReq, 172 Response: resp.TokenResponse, 173 TokenBody: body, 174 CA: ca, 175 PeerIP: auth.GetState(c).PeerIP(), 176 RequestID: trace.SpanContextFromContext(c).TraceID().String(), 177 } 178 if logErr := r.LogToken(c, &tokInfo); logErr != nil { 179 logging.WithError(logErr).Errorf(c, "Failed to insert the machine token into BigQuery log") 180 } 181 } 182 return resp, nil 183 default: 184 panic("impossible") // there's a check above 185 } 186 } 187 188 type mintTokenArgs struct { 189 Config *admin.CertificateAuthorityConfig 190 Cert *x509.Certificate 191 Request *minter.MachineTokenRequest 192 } 193 194 func (r *MintMachineTokenRPC) mintLuciMachineToken(c context.Context, args mintTokenArgs) (*minter.MintMachineTokenResponse, *tokenserver.MachineTokenBody, error) { 195 // Validate FQDN and whether it is allowed by config. The FQDN is extracted 196 // from the cert. 197 params := MintParams{ 198 Cert: args.Cert, 199 Config: args.Config, 200 Signer: r.Signer, 201 } 202 if err := params.Validate(); err != nil { 203 resp, err := r.mintingErrorResponse(c, minter.ErrorCode_BAD_TOKEN_ARGUMENTS, "%s", err) 204 return resp, nil, err 205 } 206 207 serviceVer, err := utils.ServiceVersion(c, r.Signer) 208 if err != nil { 209 return nil, nil, status.Errorf(codes.Internal, "can't grab service version - %s", err) 210 } 211 212 // Make the token. 213 switch body, signedToken, err := Mint(c, ¶ms); { 214 case err == nil: 215 expiry := time.Unix(int64(body.IssuedAt), 0).Add(time.Duration(body.Lifetime) * time.Second) 216 return &minter.MintMachineTokenResponse{ 217 ServiceVersion: serviceVer, 218 TokenResponse: &minter.MachineTokenResponse{ 219 ServiceVersion: serviceVer, 220 TokenType: &minter.MachineTokenResponse_LuciMachineToken{ 221 LuciMachineToken: &minter.LuciMachineToken{ 222 MachineToken: signedToken, 223 Expiry: timestamppb.New(expiry), 224 }, 225 }, 226 }, 227 }, body, nil 228 case transient.Tag.In(err): 229 return nil, nil, status.Errorf(codes.Internal, "failed to generate machine token - %s", err) 230 default: 231 resp, err := r.mintingErrorResponse(c, minter.ErrorCode_MACHINE_TOKEN_MINTING_ERROR, "%s", err) 232 return resp, nil, err 233 } 234 } 235 236 func (r *MintMachineTokenRPC) mintingErrorResponse(c context.Context, code minter.ErrorCode, msg string, args ...any) (*minter.MintMachineTokenResponse, error) { 237 serviceVer, err := utils.ServiceVersion(c, r.Signer) 238 if err != nil { 239 return nil, status.Errorf(codes.Internal, "can't grab service version - %s", err) 240 } 241 return &minter.MintMachineTokenResponse{ 242 ErrorCode: code, 243 ErrorMessage: fmt.Sprintf(msg, args...), 244 ServiceVersion: serviceVer, 245 }, nil 246 }