github.com/rigado/snapd@v2.42.5-go-mod+incompatible/asserts/repair_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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  	"io/ioutil"
    25  	"os/exec"
    26  	"path/filepath"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/snapcore/snapd/asserts"
    31  	. "gopkg.in/check.v1"
    32  )
    33  
    34  var (
    35  	_ = Suite(&repairSuite{})
    36  )
    37  
    38  type repairSuite struct {
    39  	modelsLine string
    40  	ts         time.Time
    41  	tsLine     string
    42  
    43  	repairStr string
    44  }
    45  
    46  const script = `#!/bin/sh
    47  set -e
    48  echo "Unpack embedded payload"
    49  match=$(grep --text --line-number '^PAYLOAD:$' $0 | cut -d ':' -f 1)
    50  payload_start=$((match + 1))
    51  # Using "base64" as its part of coreutils which should be available
    52  # everywhere
    53  tail -n +$payload_start $0 | base64 --decode - | tar -xzf -
    54  # run embedded content
    55  ./hello
    56  exit 0
    57  # payload generated with, may contain binary data
    58  #   printf '#!/bin/sh\necho hello from the inside\n' > hello
    59  #   chmod +x hello
    60  #   tar czf - hello | base64 -
    61  PAYLOAD:
    62  H4sIAJJt+FgAA+3STQrCMBDF8ax7ihEP0CkxyXn8iCZQE2jr/W11Iwi6KiL8f5u3mLd4i0mx76tZ
    63  l86Cc0t2welrPu2c6awGr95bG4x26rw1oivveriN034QMfFSy6fet/uf2m7aQy7tmJp4TFXS8g5y
    64  HupVphQllzGfYvPrkQAAAAAAAAAAAAAAAACAN3dTp9TNACgAAA==
    65  `
    66  
    67  var repairExample = fmt.Sprintf("type: repair\n"+
    68  	"authority-id: acme\n"+
    69  	"brand-id: acme\n"+
    70  	"summary: example repair\n"+
    71  	"architectures:\n"+
    72  	"  - amd64\n"+
    73  	"  - arm64\n"+
    74  	"repair-id: 42\n"+
    75  	"series:\n"+
    76  	"  - 16\n"+
    77  	"MODELSLINE"+
    78  	"TSLINE"+
    79  	"body-length: %v\n"+
    80  	"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij"+
    81  	"\n\n"+
    82  	script+"\n\n"+
    83  	"AXNpZw==", len(script))
    84  
    85  func (s *repairSuite) SetUpTest(c *C) {
    86  	s.modelsLine = "models:\n  - acme/frobinator\n"
    87  	s.ts = time.Now().Truncate(time.Second).UTC()
    88  	s.tsLine = "timestamp: " + s.ts.Format(time.RFC3339) + "\n"
    89  
    90  	s.repairStr = strings.Replace(repairExample, "MODELSLINE", s.modelsLine, 1)
    91  	s.repairStr = strings.Replace(s.repairStr, "TSLINE", s.tsLine, 1)
    92  }
    93  
    94  func (s *repairSuite) TestDecodeOK(c *C) {
    95  	a, err := asserts.Decode([]byte(s.repairStr))
    96  	c.Assert(err, IsNil)
    97  	c.Check(a.Type(), Equals, asserts.RepairType)
    98  	repair := a.(*asserts.Repair)
    99  	c.Check(repair.Timestamp(), Equals, s.ts)
   100  	c.Check(repair.BrandID(), Equals, "acme")
   101  	c.Check(repair.RepairID(), Equals, 42)
   102  	c.Check(repair.Summary(), Equals, "example repair")
   103  	c.Check(repair.Series(), DeepEquals, []string{"16"})
   104  	c.Check(repair.Architectures(), DeepEquals, []string{"amd64", "arm64"})
   105  	c.Check(repair.Models(), DeepEquals, []string{"acme/frobinator"})
   106  	c.Check(string(repair.Body()), Equals, script)
   107  }
   108  
   109  const (
   110  	repairErrPrefix = "assertion repair: "
   111  )
   112  
   113  func (s *repairSuite) TestDisabled(c *C) {
   114  	disabledTests := []struct {
   115  		disabled, expectedErr string
   116  		dis                   bool
   117  	}{
   118  		{"true", "", true},
   119  		{"false", "", false},
   120  		{"foo", `"disabled" header must be 'true' or 'false'`, false},
   121  	}
   122  
   123  	for _, test := range disabledTests {
   124  		repairStr := strings.Replace(repairExample, "MODELSLINE", fmt.Sprintf("disabled: %s\n", test.disabled), 1)
   125  		repairStr = strings.Replace(repairStr, "TSLINE", s.tsLine, 1)
   126  
   127  		a, err := asserts.Decode([]byte(repairStr))
   128  		if test.expectedErr != "" {
   129  			c.Check(err, ErrorMatches, repairErrPrefix+test.expectedErr)
   130  		} else {
   131  			c.Assert(err, IsNil)
   132  			repair := a.(*asserts.Repair)
   133  			c.Check(repair.Disabled(), Equals, test.dis)
   134  		}
   135  	}
   136  }
   137  
   138  func (s *repairSuite) TestDecodeInvalid(c *C) {
   139  	invalidTests := []struct{ original, invalid, expectedErr string }{
   140  		{"series:\n  - 16\n", "series: \n", `"series" header must be a list of strings`},
   141  		{"series:\n  - 16\n", "series: something\n", `"series" header must be a list of strings`},
   142  		{"architectures:\n  - amd64\n  - arm64\n", "architectures: foo\n", `"architectures" header must be a list of strings`},
   143  		{"models:\n  - acme/frobinator\n", "models: \n", `"models" header must be a list of strings`},
   144  		{"models:\n  - acme/frobinator\n", "models: something\n", `"models" header must be a list of strings`},
   145  		{"repair-id: 42\n", "repair-id: no-number\n", `"repair-id" header contains invalid characters: "no-number"`},
   146  		{"repair-id: 42\n", "repair-id: 0\n", `"repair-id" header contains invalid characters: "0"`},
   147  		{"repair-id: 42\n", "repair-id: 01\n", `"repair-id" header contains invalid characters: "01"`},
   148  		{"repair-id: 42\n", "repair-id: 99999999999999999999\n", `repair-id too large:.*`},
   149  		{"brand-id: acme\n", "brand-id: brand-id-not-eq-authority-id\n", `authority-id and brand-id must match, repair assertions are expected to be signed by the brand: "acme" != "brand-id-not-eq-authority-id"`},
   150  		{"summary: example repair\n", "", `"summary" header is mandatory`},
   151  		{"summary: example repair\n", "summary: \n", `"summary" header should not be empty`},
   152  		{"summary: example repair\n", "summary:\n    multi\n    line\n", `"summary" header cannot have newlines`},
   153  		{s.tsLine, "", `"timestamp" header is mandatory`},
   154  		{s.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
   155  		{s.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
   156  	}
   157  
   158  	for _, test := range invalidTests {
   159  		invalid := strings.Replace(s.repairStr, test.original, test.invalid, 1)
   160  		_, err := asserts.Decode([]byte(invalid))
   161  		c.Check(err, ErrorMatches, repairErrPrefix+test.expectedErr)
   162  	}
   163  }
   164  
   165  // FIXME: move to a different layer later
   166  func (s *repairSuite) TestRepairCanEmbeddScripts(c *C) {
   167  	a, err := asserts.Decode([]byte(s.repairStr))
   168  	c.Assert(err, IsNil)
   169  	c.Check(a.Type(), Equals, asserts.RepairType)
   170  	repair := a.(*asserts.Repair)
   171  
   172  	tmpdir := c.MkDir()
   173  	repairScript := filepath.Join(tmpdir, "repair")
   174  	err = ioutil.WriteFile(repairScript, []byte(repair.Body()), 0755)
   175  	c.Assert(err, IsNil)
   176  	cmd := exec.Command(repairScript)
   177  	cmd.Dir = tmpdir
   178  	output, err := cmd.CombinedOutput()
   179  	c.Check(err, IsNil)
   180  	c.Check(string(output), Equals, `Unpack embedded payload
   181  hello from the inside
   182  `)
   183  }