gitee.com/mysnapcore/mysnapd@v0.1.0/asserts/system_user_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package asserts_test
    21  
    22  import (
    23  	"fmt"
    24  	"strings"
    25  	"time"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"gitee.com/mysnapcore/mysnapd/asserts"
    30  )
    31  
    32  var (
    33  	_ = Suite(&systemUserSuite{})
    34  )
    35  
    36  type systemUserSuite struct {
    37  	until            time.Time
    38  	untilLine        string
    39  	since            time.Time
    40  	sinceLine        string
    41  	userPresenceLine string
    42  
    43  	formatLine string
    44  	modelsLine string
    45  
    46  	systemUserStr string
    47  }
    48  
    49  const systemUserExample = "type: system-user\n" +
    50  	"FORMATLINE\n" +
    51  	"authority-id: canonical\n" +
    52  	"brand-id: canonical\n" +
    53  	"email: foo@example.com\n" +
    54  	"series:\n" +
    55  	"  - 16\n" +
    56  	"MODELSLINE\n" +
    57  	"name: Nice Guy\n" +
    58  	"username: guy\n" +
    59  	"password: $6$salt$hash\n" +
    60  	"ssh-keys:\n" +
    61  	"  - ssh-rsa AAAABcdefg\n" +
    62  	"SINCELINE\n" +
    63  	"UNTILLINE\n" +
    64  	"USERVALIDFOR\n" +
    65  	"body-length: 0\n" +
    66  	"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
    67  	"\n\n" +
    68  	"AXNpZw=="
    69  
    70  func (s *systemUserSuite) SetUpTest(c *C) {
    71  	s.since = time.Now().Truncate(time.Second)
    72  	s.sinceLine = fmt.Sprintf("since: %s\n", s.since.Format(time.RFC3339))
    73  	s.until = time.Now().AddDate(0, 1, 0).Truncate(time.Second)
    74  	s.untilLine = fmt.Sprintf("until: %s\n", s.until.Format(time.RFC3339))
    75  	s.modelsLine = "models:\n  - frobinator\n"
    76  	s.formatLine = "format: 0\n"
    77  	s.userPresenceLine = "user-presence: \n"
    78  	s.systemUserStr = strings.Replace(systemUserExample, "UNTILLINE\n", s.untilLine, 1)
    79  	s.systemUserStr = strings.Replace(s.systemUserStr, "SINCELINE\n", s.sinceLine, 1)
    80  	s.systemUserStr = strings.Replace(s.systemUserStr, "MODELSLINE\n", s.modelsLine, 1)
    81  	s.systemUserStr = strings.Replace(s.systemUserStr, "FORMATLINE\n", s.formatLine, 1)
    82  	s.systemUserStr = strings.Replace(s.systemUserStr, "USERVALIDFOR\n", s.userPresenceLine, 1)
    83  }
    84  
    85  func (s *systemUserSuite) TestDecodeOK(c *C) {
    86  	a, err := asserts.Decode([]byte(s.systemUserStr))
    87  	c.Assert(err, IsNil)
    88  	c.Check(a.Type(), Equals, asserts.SystemUserType)
    89  	systemUser := a.(*asserts.SystemUser)
    90  	c.Check(systemUser.BrandID(), Equals, "canonical")
    91  	c.Check(systemUser.Email(), Equals, "foo@example.com")
    92  	c.Check(systemUser.Series(), DeepEquals, []string{"16"})
    93  	c.Check(systemUser.Models(), DeepEquals, []string{"frobinator"})
    94  	c.Check(systemUser.Name(), Equals, "Nice Guy")
    95  	c.Check(systemUser.Username(), Equals, "guy")
    96  	c.Check(systemUser.Password(), Equals, "$6$salt$hash")
    97  	c.Check(systemUser.SSHKeys(), DeepEquals, []string{"ssh-rsa AAAABcdefg"})
    98  	c.Check(systemUser.Since().Equal(s.since), Equals, true)
    99  	c.Check(systemUser.Until().Equal(s.until), Equals, true)
   100  	c.Check(systemUser.UserExpiration().IsZero(), Equals, true)
   101  }
   102  
   103  func (s *systemUserSuite) TestDecodePasswd(c *C) {
   104  	validTests := []struct{ original, valid string }{
   105  		{"password: $6$salt$hash\n", "password: $6$rounds=9999$salt$hash\n"},
   106  		{"password: $6$salt$hash\n", ""},
   107  	}
   108  	for _, test := range validTests {
   109  		valid := strings.Replace(s.systemUserStr, test.original, test.valid, 1)
   110  		_, err := asserts.Decode([]byte(valid))
   111  		c.Check(err, IsNil)
   112  	}
   113  }
   114  
   115  func (s *systemUserSuite) TestDecodeForcePasswdChange(c *C) {
   116  
   117  	old := "password: $6$salt$hash\n"
   118  	new := "password: $6$salt$hash\nforce-password-change: true\n"
   119  
   120  	valid := strings.Replace(s.systemUserStr, old, new, 1)
   121  	a, err := asserts.Decode([]byte(valid))
   122  	c.Check(err, IsNil)
   123  	systemUser := a.(*asserts.SystemUser)
   124  	c.Check(systemUser.ForcePasswordChange(), Equals, true)
   125  }
   126  
   127  func (s *systemUserSuite) TestValidAt(c *C) {
   128  	a, err := asserts.Decode([]byte(s.systemUserStr))
   129  	c.Assert(err, IsNil)
   130  	su := a.(*asserts.SystemUser)
   131  
   132  	c.Check(su.ValidAt(su.Since()), Equals, true)
   133  	c.Check(su.ValidAt(su.Since().AddDate(0, 0, -1)), Equals, false)
   134  	c.Check(su.ValidAt(su.Since().AddDate(0, 0, 1)), Equals, true)
   135  
   136  	c.Check(su.ValidAt(su.Until()), Equals, false)
   137  	c.Check(su.ValidAt(su.Until().AddDate(0, -1, 0)), Equals, true)
   138  	c.Check(su.ValidAt(su.Until().AddDate(0, 1, 0)), Equals, false)
   139  }
   140  
   141  func (s *systemUserSuite) TestValidAtRevoked(c *C) {
   142  	// With since == until, i.e. system-user has been revoked.
   143  	revoked := strings.Replace(s.systemUserStr, s.sinceLine, fmt.Sprintf("since: %s\n", s.until.Format(time.RFC3339)), 1)
   144  	a, err := asserts.Decode([]byte(revoked))
   145  	c.Assert(err, IsNil)
   146  	su := a.(*asserts.SystemUser)
   147  
   148  	c.Check(su.ValidAt(su.Since()), Equals, false)
   149  	c.Check(su.ValidAt(su.Since().AddDate(0, 0, -1)), Equals, false)
   150  	c.Check(su.ValidAt(su.Since().AddDate(0, 0, 1)), Equals, false)
   151  
   152  	c.Check(su.ValidAt(su.Until()), Equals, false)
   153  	c.Check(su.ValidAt(su.Until().AddDate(0, -1, 0)), Equals, false)
   154  	c.Check(su.ValidAt(su.Until().AddDate(0, 1, 0)), Equals, false)
   155  }
   156  
   157  const (
   158  	systemUserErrPrefix = "assertion system-user: "
   159  )
   160  
   161  func (s *systemUserSuite) TestDecodeInvalid(c *C) {
   162  	invalidTests := []struct{ original, invalid, expectedErr string }{
   163  		{"brand-id: canonical\n", "", `"brand-id" header is mandatory`},
   164  		{"brand-id: canonical\n", "brand-id: \n", `"brand-id" header should not be empty`},
   165  		{"email: foo@example.com\n", "", `"email" header is mandatory`},
   166  		{"email: foo@example.com\n", "email: \n", `"email" header should not be empty`},
   167  		{"email: foo@example.com\n", "email: <alice!example.com>\n", `"email" header must be a RFC 5322 compliant email address: mail: missing @ in addr-spec`},
   168  		{"email: foo@example.com\n", "email: no-mail\n", `"email" header must be a RFC 5322 compliant email address:.*`},
   169  		{"series:\n  - 16\n", "series: \n", `"series" header must be a list of strings`},
   170  		{"series:\n  - 16\n", "series: something\n", `"series" header must be a list of strings`},
   171  		{"models:\n  - frobinator\n", "models: \n", `"models" header must be a list of strings`},
   172  		{"models:\n  - frobinator\n", "models: something\n", `"models" header must be a list of strings`},
   173  		{"ssh-keys:\n  - ssh-rsa AAAABcdefg\n", "ssh-keys: \n", `"ssh-keys" header must be a list of strings`},
   174  		{"ssh-keys:\n  - ssh-rsa AAAABcdefg\n", "ssh-keys: something\n", `"ssh-keys" header must be a list of strings`},
   175  		{"name: Nice Guy\n", "name:\n  - foo\n", `"name" header must be a string`},
   176  		{"username: guy\n", "username:\n  - foo\n", `"username" header must be a string`},
   177  		{"username: guy\n", "username: bäää\n", `"username" header contains invalid characters: "bäää"`},
   178  		{"username: guy\n", "", `"username" header is mandatory`},
   179  		{"password: $6$salt$hash\n", "password:\n  - foo\n", `"password" header must be a string`},
   180  		{"password: $6$salt$hash\n", "password: cleartext\n", `"password" header invalid: hashed password must be of the form "\$integer-id\$salt\$hash", see crypt\(3\)`},
   181  		{"password: $6$salt$hash\n", "password: $ni!$salt$hash\n", `"password" header must start with "\$integer-id\$", got "ni!"`},
   182  		{"password: $6$salt$hash\n", "password: $3$salt$hash\n", `"password" header only supports \$id\$ values of 6 \(sha512crypt\) or higher`},
   183  		{"password: $6$salt$hash\n", "password: $7$invalid-salt$hash\n", `"password" header has invalid chars in salt "invalid-salt"`},
   184  		{"password: $6$salt$hash\n", "password: $8$salt$invalid-hash\n", `"password" header has invalid chars in hash "invalid-hash"`},
   185  		{"password: $6$salt$hash\n", "password: $8$rounds=9999$hash\n", `"password" header invalid: missing hash field`},
   186  		{"password: $6$salt$hash\n", "password: $8$rounds=xxx$salt$hash\n", `"password" header has invalid number of rounds:.*`},
   187  		{"password: $6$salt$hash\n", "password: $8$rounds=1$salt$hash\n", `"password" header rounds parameter out of bounds: 1`},
   188  		{"password: $6$salt$hash\n", "password: $8$rounds=1999999999$salt$hash\n", `"password" header rounds parameter out of bounds: 1999999999`},
   189  		{"password: $6$salt$hash\n", "force-password-change: true\n", `cannot use "force-password-change" with an empty "password"`},
   190  		{"password: $6$salt$hash\n", "password: $6$salt$hash\nforce-password-change: xxx\n", `"force-password-change" header must be 'true' or 'false'`},
   191  		{s.sinceLine, "since: \n", `"since" header should not be empty`},
   192  		{s.sinceLine, "since: 12:30\n", `"since" header is not a RFC3339 date: .*`},
   193  		{s.untilLine, "until: \n", `"until" header should not be empty`},
   194  		{s.untilLine, "until: 12:30\n", `"until" header is not a RFC3339 date: .*`},
   195  		{s.untilLine, "until: 1002-11-01T22:08:41+00:00\n", `'until' time cannot be before 'since' time`},
   196  		{s.modelsLine, s.modelsLine + "serials: \n", `"serials" header must be a list of strings`},
   197  		{s.modelsLine, s.modelsLine + "serials: something\n", `"serials" header must be a list of strings`},
   198  		{s.modelsLine, s.modelsLine + "serials:\n  - 7c7f435d-ed28-4281-bd77-e271e0846904\n", `the "serials" header is only supported for format 1 or greater`},
   199  		{s.userPresenceLine, "user-presence: until-expiration\n", `the "user-presence" header is only supported for format 2 or greater`},
   200  	}
   201  
   202  	for _, test := range invalidTests {
   203  		invalid := strings.Replace(s.systemUserStr, test.original, test.invalid, 1)
   204  		_, err := asserts.Decode([]byte(invalid))
   205  		c.Check(err, ErrorMatches, systemUserErrPrefix+test.expectedErr)
   206  	}
   207  }
   208  
   209  func (s *systemUserSuite) TestUntilNoModels(c *C) {
   210  	// no models is good for <1y
   211  	su := strings.Replace(s.systemUserStr, s.modelsLine, "", -1)
   212  	_, err := asserts.Decode([]byte(su))
   213  	c.Check(err, IsNil)
   214  
   215  	// but invalid for more than one year
   216  	oneYearPlusOne := time.Now().AddDate(1, 0, 1).Truncate(time.Second)
   217  	su = strings.Replace(su, s.untilLine, fmt.Sprintf("until: %s\n", oneYearPlusOne.Format(time.RFC3339)), -1)
   218  	_, err = asserts.Decode([]byte(su))
   219  	c.Check(err, ErrorMatches, systemUserErrPrefix+"'until' time cannot be more than 365 days in the future when no models are specified")
   220  }
   221  
   222  func (s *systemUserSuite) TestUntilWithModels(c *C) {
   223  	// with models it can be valid forever
   224  	oneYearPlusOne := time.Now().AddDate(10, 0, 1).Truncate(time.Second)
   225  	su := strings.Replace(s.systemUserStr, s.untilLine, fmt.Sprintf("until: %s\n", oneYearPlusOne.Format(time.RFC3339)), -1)
   226  	_, err := asserts.Decode([]byte(su))
   227  	c.Check(err, IsNil)
   228  }
   229  
   230  // The following tests deal with "format: 1" which adds support for
   231  // tying system-user assertions to device serials.
   232  
   233  var serialsLine = "serials:\n  - 7c7f435d-ed28-4281-bd77-e271e0846904\n"
   234  
   235  func (s *systemUserSuite) TestDecodeInvalidFormat1Serials(c *C) {
   236  	s.systemUserStr = strings.Replace(s.systemUserStr, s.formatLine, "format: 1\n", 1)
   237  	serialWithMultipleModels := "models:\n  - m1\n  - m2\n" + serialsLine
   238  
   239  	invalidTests := []struct{ original, invalid, expectedErr string }{
   240  		{s.modelsLine, serialWithMultipleModels, `in the presence of the "serials" header "models" must specify exactly one model`},
   241  	}
   242  	for _, test := range invalidTests {
   243  		invalid := strings.Replace(s.systemUserStr, test.original, test.invalid, 1)
   244  		_, err := asserts.Decode([]byte(invalid))
   245  		c.Check(err, ErrorMatches, systemUserErrPrefix+test.expectedErr)
   246  	}
   247  }
   248  
   249  func (s *systemUserSuite) TestDecodeOKFormat1Serials(c *C) {
   250  	s.systemUserStr = strings.Replace(s.systemUserStr, s.formatLine, "format: 1\n", 1)
   251  
   252  	s.systemUserStr = strings.Replace(s.systemUserStr, s.modelsLine, s.modelsLine+serialsLine, 1)
   253  	a, err := asserts.Decode([]byte(s.systemUserStr))
   254  	c.Assert(err, IsNil)
   255  	c.Check(a.Type(), Equals, asserts.SystemUserType)
   256  	systemUser := a.(*asserts.SystemUser)
   257  	// just for double checking, already covered by "format: 0" tests
   258  	c.Check(systemUser.BrandID(), Equals, "canonical")
   259  	// new in "format: 1"
   260  	c.Check(systemUser.Serials(), DeepEquals, []string{"7c7f435d-ed28-4281-bd77-e271e0846904"})
   261  
   262  }
   263  
   264  func (s *systemUserSuite) TestDecodeInvalidFormat2UserPresence(c *C) {
   265  	s.systemUserStr = strings.Replace(s.systemUserStr, s.formatLine, "format: 2\n", 1)
   266  
   267  	invalidTests := []struct{ original, invalid, expectedErr string }{
   268  		{s.userPresenceLine, "user-presence: tomorrow\n", `invalid "user-presence" header, only explicit valid value is "until-expiration": "tomorrow"`},
   269  		{s.userPresenceLine, "user-presence: 0\n", `invalid "user-presence" header, only explicit valid value is "until-expiration": "0"`},
   270  	}
   271  	for _, test := range invalidTests {
   272  		invalid := strings.Replace(s.systemUserStr, test.original, test.invalid, 1)
   273  		_, err := asserts.Decode([]byte(invalid))
   274  		c.Check(err, ErrorMatches, systemUserErrPrefix+test.expectedErr)
   275  	}
   276  }
   277  
   278  func (s *systemUserSuite) TestDecodeOKFormat2UserPresence(c *C) {
   279  	s.systemUserStr = strings.Replace(s.systemUserStr, s.formatLine, "format: 2\n", 1)
   280  
   281  	s.systemUserStr = strings.Replace(s.systemUserStr, s.userPresenceLine, "user-presence: until-expiration\n", 1)
   282  	a, err := asserts.Decode([]byte(s.systemUserStr))
   283  	c.Assert(err, IsNil)
   284  	c.Check(a.Type(), Equals, asserts.SystemUserType)
   285  	systemUser := a.(*asserts.SystemUser)
   286  	// new in "format: 2"
   287  	c.Check(systemUser.UserExpiration().Equal(systemUser.Until()), Equals, true)
   288  }
   289  
   290  func (s *systemUserSuite) TestSuggestedFormat(c *C) {
   291  	fmtnum, err := asserts.SuggestFormat(asserts.SystemUserType, nil, nil)
   292  	c.Assert(err, IsNil)
   293  	c.Check(fmtnum, Equals, 0)
   294  
   295  	headers := map[string]interface{}{
   296  		"serials": []interface{}{"serialserial"},
   297  	}
   298  	fmtnum, err = asserts.SuggestFormat(asserts.SystemUserType, headers, nil)
   299  	c.Assert(err, IsNil)
   300  	c.Check(fmtnum, Equals, 1)
   301  
   302  	headers = map[string]interface{}{
   303  		"user-presence": "until-expiration",
   304  	}
   305  	fmtnum, err = asserts.SuggestFormat(asserts.SystemUserType, headers, nil)
   306  	c.Assert(err, IsNil)
   307  	c.Check(fmtnum, Equals, 2)
   308  }