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, &params); {
   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  }