mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
* asserts/model: add preseedAuthority field to Model * seed20: allow authority-id to differ from the brand-id * fixup! asserts/model: add preseedAuthority field to Model fix comment wording to PreseedAuthority * fixup! seed20: allow authority-id to differ from the brand-id clarify error message as "preseed authority-id" * fixup! asserts/model: add preseedAuthority field to Model standardize checkOptionalAuthority() signature and make acceptsAny bool explicit when invoking it * fixup! seed20: allow authority-id to differ from the brand-id fix ineffectual assignment to preseedAs2 * fixup! asserts/model: add preseedAuthority field to Model bump copyright years for files touched by 5593e76312 * fixup! seed20: allow authority-id to differ from the brand-id bump copyright years for files touched by ce7ba34e0f * fixup! asserts/model: add preseedAuthority field to Model asserts/model.go: rename "acceptsAny" to "acceptsWildcard"
227 lines
6.0 KiB
Go
227 lines
6.0 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2023 Canonical Ltd
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package asserts
|
|
|
|
import (
|
|
"crypto"
|
|
"fmt"
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/snapcore/snapd/snap/naming"
|
|
)
|
|
|
|
// validSystemLabel is the regex describing a valid system label. Typically
|
|
// system labels are expected to be date based, eg. 20201116, but for
|
|
// completeness follow the same rule as model names (incl. one letter model
|
|
// names and thus system labels), with the exception that uppercase letters are
|
|
// not allowed, as the systems will often be stored in a FAT filesystem.
|
|
var validSystemLabel = regexp.MustCompile("^[a-z0-9](?:-?[a-z0-9])*$")
|
|
|
|
// IsValidSystemLabel checks whether the string is a valid UC20 seed system
|
|
// label.
|
|
func IsValidSystemLabel(label string) error {
|
|
if !validSystemLabel.MatchString(label) {
|
|
return fmt.Errorf("invalid seed system label: %q", label)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PreseedSnap holds the details about a snap constrained by a preseed assertion.
|
|
type PreseedSnap struct {
|
|
Name string
|
|
SnapID string
|
|
Revision int
|
|
}
|
|
|
|
// SnapName implements naming.SnapRef.
|
|
func (s *PreseedSnap) SnapName() string {
|
|
return s.Name
|
|
}
|
|
|
|
// ID implements naming.SnapRef.
|
|
func (s *PreseedSnap) ID() string {
|
|
return s.SnapID
|
|
}
|
|
|
|
// Preseed holds preseed assertion, which is a statement about system-label,
|
|
// model, set of snaps and preseed artifact used for preseeding of UC20 system.
|
|
type Preseed struct {
|
|
assertionBase
|
|
snaps []*PreseedSnap
|
|
timestamp time.Time
|
|
}
|
|
|
|
// Series returns the series that this assertion is valid for.
|
|
func (p *Preseed) Series() string {
|
|
return p.HeaderString("series")
|
|
}
|
|
|
|
// BrandID returns the brand identifier.
|
|
func (p *Preseed) BrandID() string {
|
|
return p.HeaderString("brand-id")
|
|
}
|
|
|
|
// Model returns the model name identifier.
|
|
func (p *Preseed) Model() string {
|
|
return p.HeaderString("model")
|
|
}
|
|
|
|
// SystemLabel returns the label of the seeded system.
|
|
func (p *Preseed) SystemLabel() string {
|
|
return p.HeaderString("system-label")
|
|
}
|
|
|
|
// Timestamp returns the time when the preseed assertion was issued.
|
|
func (p *Preseed) Timestamp() time.Time {
|
|
return p.timestamp
|
|
}
|
|
|
|
// ArtifactSHA3_384 returns the checksum of preseeding artifact.
|
|
func (p *Preseed) ArtifactSHA3_384() string {
|
|
return p.HeaderString("artifact-sha3-384")
|
|
}
|
|
|
|
// Snaps returns the snaps for preseeding.
|
|
func (p *Preseed) Snaps() []*PreseedSnap {
|
|
return p.snaps
|
|
}
|
|
|
|
func checkPreseedSnap(snap map[string]interface{}) (*PreseedSnap, error) {
|
|
name, err := checkNotEmptyStringWhat(snap, "name", "of snap")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := naming.ValidateSnap(name); err != nil {
|
|
return nil, fmt.Errorf("invalid snap name %q", name)
|
|
}
|
|
|
|
what := fmt.Sprintf("of snap %q", name)
|
|
|
|
// snap id can be omitted if the model allows for unasserted snaps
|
|
var snapID string
|
|
if _, ok := snap["id"]; ok {
|
|
snapID, err = checkStringMatchesWhat(snap, "id", what, naming.ValidSnapID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
var snapRevision int
|
|
if _, ok := snap["revision"]; ok {
|
|
var err error
|
|
snapRevision, err = checkSnapRevisionWhat(snap, "revision", what)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if snapID != "" && snapRevision <= 0 {
|
|
return nil, fmt.Errorf("snap revision is required when snap id is set")
|
|
}
|
|
if snapID == "" && snapRevision > 0 {
|
|
return nil, fmt.Errorf("snap id is required when revision is set")
|
|
}
|
|
|
|
return &PreseedSnap{
|
|
Name: name,
|
|
SnapID: snapID,
|
|
Revision: snapRevision,
|
|
}, nil
|
|
}
|
|
|
|
func checkPreseedSnaps(snapList interface{}) ([]*PreseedSnap, error) {
|
|
const wrongHeaderType = `"snaps" header must be a list of maps`
|
|
|
|
entries, ok := snapList.([]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf(wrongHeaderType)
|
|
}
|
|
|
|
seen := make(map[string]bool, len(entries))
|
|
seenIDs := make(map[string]string, len(entries))
|
|
snaps := make([]*PreseedSnap, 0, len(entries))
|
|
for _, entry := range entries {
|
|
snap, ok := entry.(map[string]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf(wrongHeaderType)
|
|
}
|
|
preseedSnap, err := checkPreseedSnap(snap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if seen[preseedSnap.Name] {
|
|
return nil, fmt.Errorf("cannot list the same snap %q multiple times", preseedSnap.Name)
|
|
}
|
|
seen[preseedSnap.Name] = true
|
|
snapID := preseedSnap.SnapID
|
|
if snapID != "" {
|
|
if underName := seenIDs[snapID]; underName != "" {
|
|
return nil, fmt.Errorf("cannot specify the same snap id %q multiple times, specified for snaps %q and %q", snapID, underName, preseedSnap.Name)
|
|
}
|
|
seenIDs[snapID] = preseedSnap.Name
|
|
}
|
|
snaps = append(snaps, preseedSnap)
|
|
}
|
|
|
|
return snaps, nil
|
|
}
|
|
|
|
func assemblePreseed(assert assertionBase) (Assertion, error) {
|
|
// because the authority-id and model-id can differ (as per the model),
|
|
// authority-id should be validated against allowed IDs when the preseed
|
|
// blob is being checked
|
|
|
|
_, err := checkModel(assert.headers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = checkStringMatches(assert.headers, "system-label", validSystemLabel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
snapList, ok := assert.headers["snaps"]
|
|
if !ok {
|
|
return nil, fmt.Errorf(`"snaps" header is mandatory`)
|
|
}
|
|
snaps, err := checkPreseedSnaps(snapList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = checkDigest(assert.headers, "artifact-sha3-384", crypto.SHA3_384)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
timestamp, err := checkRFC3339Date(assert.headers, "timestamp")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Preseed{
|
|
assertionBase: assert,
|
|
snaps: snaps,
|
|
timestamp: timestamp,
|
|
}, nil
|
|
}
|