go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/auth/machine/auth_method_test.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 machine
    16  
    17  import (
    18  	"context"
    19  	"encoding/base64"
    20  	"fmt"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	"google.golang.org/protobuf/proto"
    26  
    27  	"go.chromium.org/luci/common/clock"
    28  	"go.chromium.org/luci/common/clock/testclock"
    29  	"go.chromium.org/luci/common/logging"
    30  	"go.chromium.org/luci/common/logging/memlogger"
    31  	"go.chromium.org/luci/server/auth"
    32  	"go.chromium.org/luci/server/auth/authtest"
    33  	"go.chromium.org/luci/server/auth/signing"
    34  	"go.chromium.org/luci/server/auth/signing/signingtest"
    35  
    36  	tokenserver "go.chromium.org/luci/tokenserver/api"
    37  
    38  	. "github.com/smartystreets/goconvey/convey"
    39  )
    40  
    41  func TestMachineTokenAuthMethod(t *testing.T) {
    42  	Convey("with mock context", t, func() {
    43  		ctx := makeTestContext()
    44  		log := logging.Get(ctx).(*memlogger.MemLogger)
    45  		signer := signingtest.NewSigner(nil)
    46  		method := MachineTokenAuthMethod{
    47  			certsFetcher: func(c context.Context, email string) (*signing.PublicCertificates, error) {
    48  				if email == "valid-signer@example.com" {
    49  					return signer.Certificates(c)
    50  				}
    51  				return nil, fmt.Errorf("unknown signer")
    52  			},
    53  		}
    54  
    55  		mint := func(tok *tokenserver.MachineTokenBody, sig []byte) string {
    56  			body, _ := proto.Marshal(tok)
    57  			keyID, validSig, _ := signer.SignBytes(ctx, body)
    58  			if sig == nil {
    59  				sig = validSig
    60  			}
    61  			envelope, _ := proto.Marshal(&tokenserver.MachineTokenEnvelope{
    62  				TokenBody: body,
    63  				KeyId:     keyID,
    64  				RsaSha256: sig,
    65  			})
    66  			return base64.RawStdEncoding.EncodeToString(envelope)
    67  		}
    68  
    69  		call := func(tok string) (*auth.User, error) {
    70  			r := authtest.NewFakeRequestMetadata()
    71  			if tok != "" {
    72  				r.FakeHeader.Set(MachineTokenHeader, tok)
    73  			}
    74  			u, _, err := method.Authenticate(ctx, r)
    75  			return u, err
    76  		}
    77  
    78  		hasLog := func(msg string) bool {
    79  			return log.HasFunc(func(m *memlogger.LogEntry) bool {
    80  				return strings.Contains(m.Msg, msg)
    81  			})
    82  		}
    83  
    84  		Convey("valid token works", func() {
    85  			user, err := call(mint(&tokenserver.MachineTokenBody{
    86  				MachineFqdn: "some-machine.location",
    87  				CaId:        123,
    88  				CertSn:      []byte{1, 2, 3},
    89  				IssuedBy:    "valid-signer@example.com",
    90  				IssuedAt:    uint64(clock.Now(ctx).Unix()),
    91  				Lifetime:    3600,
    92  			}, nil))
    93  			So(err, ShouldBeNil)
    94  			So(user, ShouldResemble, &auth.User{
    95  				Identity: "bot:some-machine.location",
    96  				Extra: &MachineTokenInfo{
    97  					FQDN:   "some-machine.location",
    98  					CA:     123,
    99  					CertSN: []byte{1, 2, 3},
   100  				},
   101  			})
   102  		})
   103  
   104  		Convey("not header => not applicable", func() {
   105  			user, err := call("")
   106  			So(user, ShouldBeNil)
   107  			So(err, ShouldBeNil)
   108  		})
   109  
   110  		Convey("not base64 envelope", func() {
   111  			_, err := call("not-a-valid-token")
   112  			So(err, ShouldEqual, ErrBadToken)
   113  			So(hasLog("Failed to deserialize the token"), ShouldBeTrue)
   114  		})
   115  
   116  		Convey("broken envelope", func() {
   117  			_, err := call("abcdef")
   118  			So(err, ShouldEqual, ErrBadToken)
   119  			So(hasLog("Failed to deserialize the token"), ShouldBeTrue)
   120  		})
   121  
   122  		Convey("broken body", func() {
   123  			envelope, _ := proto.Marshal(&tokenserver.MachineTokenEnvelope{
   124  				TokenBody: []byte("bad body"),
   125  				KeyId:     "123",
   126  				RsaSha256: []byte("12345"),
   127  			})
   128  			tok := base64.RawStdEncoding.EncodeToString(envelope)
   129  			_, err := call(tok)
   130  			So(err, ShouldEqual, ErrBadToken)
   131  			So(hasLog("Failed to deserialize the token"), ShouldBeTrue)
   132  		})
   133  
   134  		Convey("bad signer ID", func() {
   135  			_, err := call(mint(&tokenserver.MachineTokenBody{
   136  				MachineFqdn: "some-machine.location",
   137  				IssuedBy:    "not-a-email.com",
   138  				IssuedAt:    uint64(clock.Now(ctx).Unix()),
   139  				Lifetime:    3600,
   140  			}, nil))
   141  			So(err, ShouldEqual, ErrBadToken)
   142  			So(hasLog("Bad issued_by field"), ShouldBeTrue)
   143  		})
   144  
   145  		Convey("unknown signer", func() {
   146  			_, err := call(mint(&tokenserver.MachineTokenBody{
   147  				MachineFqdn: "some-machine.location",
   148  				IssuedBy:    "unknown-signer@example.com",
   149  				IssuedAt:    uint64(clock.Now(ctx).Unix()),
   150  				Lifetime:    3600,
   151  			}, nil))
   152  			So(err, ShouldEqual, ErrBadToken)
   153  			So(hasLog("Unknown token issuer"), ShouldBeTrue)
   154  		})
   155  
   156  		Convey("not yet valid", func() {
   157  			_, err := call(mint(&tokenserver.MachineTokenBody{
   158  				MachineFqdn: "some-machine.location",
   159  				IssuedBy:    "valid-signer@example.com",
   160  				IssuedAt:    uint64(clock.Now(ctx).Unix()) + 60,
   161  				Lifetime:    3600,
   162  			}, nil))
   163  			So(err, ShouldEqual, ErrBadToken)
   164  			So(hasLog("Token has expired or not yet valid"), ShouldBeTrue)
   165  		})
   166  
   167  		Convey("expired", func() {
   168  			_, err := call(mint(&tokenserver.MachineTokenBody{
   169  				MachineFqdn: "some-machine.location",
   170  				IssuedBy:    "valid-signer@example.com",
   171  				IssuedAt:    uint64(clock.Now(ctx).Unix()) - 3620,
   172  				Lifetime:    3600,
   173  			}, nil))
   174  			So(err, ShouldEqual, ErrBadToken)
   175  			So(hasLog("Token has expired or not yet valid"), ShouldBeTrue)
   176  		})
   177  
   178  		Convey("bad signature", func() {
   179  			_, err := call(mint(&tokenserver.MachineTokenBody{
   180  				MachineFqdn: "some-machine.location",
   181  				IssuedBy:    "valid-signer@example.com",
   182  				IssuedAt:    uint64(clock.Now(ctx).Unix()),
   183  				Lifetime:    3600,
   184  			}, []byte("bad signature")))
   185  			So(err, ShouldEqual, ErrBadToken)
   186  			So(hasLog("Bad signature"), ShouldBeTrue)
   187  		})
   188  
   189  		Convey("bad machine_fqdn", func() {
   190  			_, err := call(mint(&tokenserver.MachineTokenBody{
   191  				MachineFqdn: "not::valid::machine::id",
   192  				IssuedBy:    "valid-signer@example.com",
   193  				IssuedAt:    uint64(clock.Now(ctx).Unix()),
   194  				Lifetime:    3600,
   195  			}, nil))
   196  			So(err, ShouldEqual, ErrBadToken)
   197  			So(hasLog("Bad machine_fqdn"), ShouldBeTrue)
   198  		})
   199  	})
   200  }
   201  
   202  func makeTestContext() context.Context {
   203  	ctx := context.Background()
   204  	ctx, _ = testclock.UseTime(ctx, time.Date(2015, time.February, 3, 4, 5, 6, 7, time.UTC))
   205  	ctx = memlogger.Use(ctx)
   206  
   207  	return authtest.NewFakeDB(
   208  		authtest.MockMembership("user:valid-signer@example.com", TokenServersGroup),
   209  	).Use(ctx)
   210  }