go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/appengine/impl/machinetoken/machinetoken_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 machinetoken
    16  
    17  import (
    18  	"context"
    19  	"crypto/x509"
    20  	"crypto/x509/pkix"
    21  	"math/big"
    22  	"testing"
    23  	"time"
    24  
    25  	"go.chromium.org/luci/common/clock/testclock"
    26  	"go.chromium.org/luci/server/auth/signing"
    27  
    28  	tokenserver "go.chromium.org/luci/tokenserver/api"
    29  	"go.chromium.org/luci/tokenserver/api/admin/v1"
    30  
    31  	. "github.com/smartystreets/goconvey/convey"
    32  	. "go.chromium.org/luci/common/testing/assertions"
    33  )
    34  
    35  func TestMachineFQDN(t *testing.T) {
    36  	// See rpc_mocks_test.go for getTestCert, certWithCN and certWithSAN.
    37  
    38  	// Test parsing of the real certs.
    39  
    40  	Convey("MachineFQDN works for cert without SAN", t, func() {
    41  		params := MintParams{Cert: getTestCert(certWithCN)}
    42  		fqdn, err := params.MachineFQDN()
    43  		So(err, ShouldBeNil)
    44  		So(fqdn, ShouldEqual, "luci-token-server-test-1.fake.domain")
    45  	})
    46  
    47  	Convey("MachineFQDN works for cert with SAN", t, func() {
    48  		params := MintParams{Cert: getTestCert(certWithSAN)}
    49  		fqdn, err := params.MachineFQDN()
    50  		So(err, ShouldBeNil)
    51  		So(fqdn, ShouldEqual, "fuchsia-debian-dev-141242e1-us-central1-f-0psd.c.fuchsia-infra.internal")
    52  	})
    53  
    54  	Convey("MachineFQDN works for cert where CN == SAN", t, func() {
    55  		params := MintParams{Cert: getTestCert(certWithCNEqualSAN)}
    56  		fqdn, err := params.MachineFQDN()
    57  		So(err, ShouldBeNil)
    58  		So(fqdn, ShouldEqual, "proto-chrome-focal.c.chromecompute.google.com.internal")
    59  	})
    60  
    61  	Convey("MachineFQDN with more than one SAN", t, func() {
    62  		params := MintParams{
    63  			Cert: &x509.Certificate{
    64  				Subject:  pkix.Name{CommonName: "name1"},
    65  				DNSNames: []string{"name1.example.com", "name2.example.com"},
    66  			},
    67  		}
    68  		fqdn, err := params.MachineFQDN()
    69  		So(fqdn, ShouldEqual, "name1.example.com")
    70  		So(err, ShouldBeNil)
    71  	})
    72  
    73  	// Test some synthetic cases.
    74  
    75  	Convey("MachineFQDN with empty CN", t, func() {
    76  		params := MintParams{
    77  			Cert: &x509.Certificate{
    78  				DNSNames: []string{"name1.example.com"},
    79  			},
    80  		}
    81  		_, err := params.MachineFQDN()
    82  		So(err, ShouldErrLike, "unsupported cert, Subject CN field is required")
    83  	})
    84  }
    85  
    86  func TestMintParamsValidation(t *testing.T) {
    87  	Convey("with token params", t, func() {
    88  		params := MintParams{
    89  			Cert: &x509.Certificate{
    90  				Subject:      pkix.Name{CommonName: "host.domain"},
    91  				SerialNumber: big.NewInt(12345),
    92  			},
    93  			Config: &admin.CertificateAuthorityConfig{
    94  				KnownDomains: []*admin.DomainConfig{
    95  					{
    96  						Domain:               []string{"domain"},
    97  						MachineTokenLifetime: 3600,
    98  					},
    99  				},
   100  			},
   101  		}
   102  
   103  		Convey("good params", func() {
   104  			So(params.Validate(), ShouldBeNil)
   105  			fqdn, err := params.MachineFQDN()
   106  			So(err, ShouldBeNil)
   107  			So(fqdn, ShouldEqual, "host.domain")
   108  		})
   109  
   110  		Convey("good params with subdomain", func() {
   111  			params.Cert.Subject.CommonName = "host.subdomain.domain"
   112  			So(params.Validate(), ShouldBeNil)
   113  			fqdn, err := params.MachineFQDN()
   114  			So(err, ShouldBeNil)
   115  			So(fqdn, ShouldEqual, "host.subdomain.domain")
   116  		})
   117  
   118  		Convey("bad FQDN case is converted to lowercase", func() {
   119  			params.Cert.Subject.CommonName = "HOST.domain"
   120  			So(params.Validate(), ShouldBeNil)
   121  			fqdn, err := params.MachineFQDN()
   122  			So(err, ShouldBeNil)
   123  			So(fqdn, ShouldEqual, "host.domain")
   124  		})
   125  
   126  		Convey("bad FQDN", func() {
   127  			params.Cert.Subject.CommonName = "host"
   128  			So(params.Validate(), ShouldErrLike, "not a valid FQDN")
   129  		})
   130  
   131  		Convey("not listed", func() {
   132  			params.Cert.Subject.CommonName = "host.blah"
   133  			So(params.Validate(), ShouldErrLike, "not listed in the config")
   134  		})
   135  
   136  		Convey("tokens are not allowed", func() {
   137  			params.Config.KnownDomains[0].MachineTokenLifetime = 0
   138  			So(params.Validate(), ShouldErrLike, "are not allowed")
   139  		})
   140  
   141  		Convey("bad SN", func() {
   142  			params.Cert.SerialNumber = big.NewInt(-1)
   143  			So(params.Validate(), ShouldErrLike, "invalid certificate serial number")
   144  		})
   145  	})
   146  }
   147  
   148  func TestMint(t *testing.T) {
   149  	Convey("with mock context", t, func() {
   150  		ctx := context.Background()
   151  		ctx, _ = testclock.UseTime(ctx, time.Date(2015, time.February, 3, 4, 5, 6, 7, time.UTC))
   152  
   153  		Convey("works", func() {
   154  			params := MintParams{
   155  				Cert: &x509.Certificate{
   156  					Subject:      pkix.Name{CommonName: "host.domain"},
   157  					SerialNumber: big.NewInt(12345),
   158  				},
   159  				Config: &admin.CertificateAuthorityConfig{
   160  					KnownDomains: []*admin.DomainConfig{
   161  						{
   162  							Domain:               []string{"domain"},
   163  							MachineTokenLifetime: 3600,
   164  						},
   165  					},
   166  				},
   167  				Signer: fakeSigner{},
   168  			}
   169  			body, token, err := Mint(ctx, &params)
   170  			So(err, ShouldBeNil)
   171  			So(body, ShouldResembleProto, &tokenserver.MachineTokenBody{
   172  				MachineFqdn: "host.domain",
   173  				IssuedBy:    "token-server@example.com",
   174  				IssuedAt:    1422936306,
   175  				Lifetime:    3600,
   176  				CaId:        0,
   177  				CertSn:      big.NewInt(12345).Bytes(),
   178  			})
   179  			So(token, ShouldEqual, "CjQKC2hvc3QuZG9tYWluEhh0b2tlbi1zZXJ2ZXJAZXhhbXB"+
   180  				"sZS5jb20Y8pHBpgUgkBw6AjA5EgZrZXlfaWQaCXNpZ25hdHVyZQ")
   181  		})
   182  	})
   183  }
   184  
   185  type fakeSigner struct{}
   186  
   187  func (fakeSigner) SignBytes(c context.Context, blob []byte) (keyID string, sig []byte, err error) {
   188  	return "key_id", []byte("signature"), nil
   189  }
   190  
   191  func (fakeSigner) Certificates(c context.Context) (*signing.PublicCertificates, error) {
   192  	panic("not implemented yet")
   193  }
   194  
   195  func (fakeSigner) ServiceInfo(c context.Context) (*signing.ServiceInfo, error) {
   196  	return &signing.ServiceInfo{
   197  		ServiceAccountName: "token-server@example.com",
   198  	}, nil
   199  }