Adjust all paces that call `Solver.Depsolve()`, to cope with the changes that enabled SBOM support. Fix loading of testing repositories in the CloudAPI unit tests. Signed-off-by: Tomáš Hozza <thozza@redhat.com>
262 lines
7.7 KiB
Go
262 lines
7.7 KiB
Go
package ostree
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/osbuild/images/pkg/rhsm"
|
|
)
|
|
|
|
var (
|
|
ostreeRefRE = regexp.MustCompile(`^(?:[\w\d][-._\w\d]*\/)*[\w\d][-._\w\d]*$`)
|
|
ostreeCommitRE = regexp.MustCompile("^[0-9a-f]{64}$")
|
|
)
|
|
|
|
// SourceSpec serves as input for ResolveParams, and contains all necessary
|
|
// variables to resolve a ref, which can then be turned into a CommitSpec.
|
|
type SourceSpec struct {
|
|
URL string
|
|
Ref string
|
|
RHSM bool
|
|
}
|
|
|
|
// CommitSpec specifies an ostree commit using any combination of Ref (branch), URL (source), and Checksum (commit ID).
|
|
type CommitSpec struct {
|
|
|
|
// Ref for the commit. Can be empty.
|
|
Ref string
|
|
|
|
// URL of the repo where the commit can be fetched, if available.
|
|
URL string
|
|
|
|
ContentURL string
|
|
|
|
Secrets string
|
|
|
|
// Checksum of the commit.
|
|
Checksum string
|
|
}
|
|
|
|
// ImageOptions specify an ostree ref, checksum, URL, ContentURL, and RHSM. The
|
|
// meaning of each parameter depends on the image type being built. The type
|
|
// is used to specify ostree-related image options when initializing a
|
|
// Manifest.
|
|
type ImageOptions struct {
|
|
// For ostree commit and container types: The ref of the new commit to be
|
|
// built.
|
|
// For ostree installers and raw images: The ref of the commit being
|
|
// embedded in the installer or deployed in the image.
|
|
ImageRef string `json:"ref"`
|
|
|
|
// For ostree commit and container types: The ParentRef specifies the parent
|
|
// ostree commit that the new commit will be based on.
|
|
// For ostree installers and raw images: The ParentRef does not apply.
|
|
ParentRef string `json:"parent"`
|
|
|
|
// The URL from which to fetch the commit specified by the checksum.
|
|
URL string `json:"url"`
|
|
|
|
// If specified, the URL will be used only for metadata.
|
|
ContentURL string `json:"contenturl"`
|
|
|
|
// Indicate if the 'org.osbuild.rhsm.consumer' secret should be added when pulling from the
|
|
// remote.
|
|
RHSM bool `json:"rhsm"`
|
|
}
|
|
|
|
// Validate the image options. This doesn't verify the existence of any remote
|
|
// objects and does not guarantee that refs will be successfully resolved. It
|
|
// only checks that the values and value combinations are valid.
|
|
//
|
|
// The function checks the following:
|
|
// - The ImageRef, if specified, is a valid ref and does not look like a
|
|
// checksum.
|
|
// - The ParentRef, if specified, must be a valid ref or a checksum.
|
|
// - If the ParentRef is specified, the URL must also be specified.
|
|
// - URLs must be valid.
|
|
func (options ImageOptions) Validate() error {
|
|
if ref := options.ImageRef; ref != "" {
|
|
// image ref must not look like a checksum
|
|
if verifyChecksum(ref) {
|
|
return NewRefError("ostree image ref looks like a checksum %q", ref)
|
|
}
|
|
if !verifyRef(ref) {
|
|
return NewRefError("invalid ostree image ref %q", ref)
|
|
}
|
|
}
|
|
|
|
if parent := options.ParentRef; parent != "" {
|
|
if !verifyChecksum(parent) && !verifyRef(parent) {
|
|
return NewRefError("invalid ostree parent ref or commit %q", parent)
|
|
}
|
|
|
|
// valid URL required
|
|
if purl := options.URL; purl == "" {
|
|
return NewParameterComboError("ostree parent ref specified, but no URL to retrieve it")
|
|
}
|
|
}
|
|
|
|
// whether required or not, any URL specified must be valid
|
|
if purl := options.URL; purl != "" {
|
|
if _, err := url.ParseRequestURI(purl); err != nil {
|
|
return fmt.Errorf("ostree URL %q is invalid", purl)
|
|
}
|
|
}
|
|
|
|
if curl := options.ContentURL; curl != "" {
|
|
if _, err := url.ParseRequestURI(curl); err != nil {
|
|
return fmt.Errorf("ostree content URL %q is invalid", curl)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Remote defines the options that can be set for an OSTree Remote configuration.
|
|
type Remote struct {
|
|
Name string
|
|
URL string
|
|
ContentURL string
|
|
GPGKeyPaths []string
|
|
}
|
|
|
|
func verifyRef(ref string) bool {
|
|
return len(ref) > 0 && ostreeRefRE.MatchString(ref)
|
|
}
|
|
|
|
func verifyChecksum(commit string) bool {
|
|
return len(commit) > 0 && ostreeCommitRE.MatchString(commit)
|
|
}
|
|
|
|
// ResolveRef resolves the URL path specified by the location and ref
|
|
// (location+"refs/heads/"+ref) and returns the commit ID for the named ref. If
|
|
// there is an error, it will be of type ResolveRefError.
|
|
func ResolveRef(location, ref string, consumerCerts bool, subs *rhsm.Subscriptions, ca *string) (string, error) {
|
|
u, err := url.Parse(location)
|
|
if err != nil {
|
|
return "", NewResolveRefError(fmt.Sprintf("error parsing ostree repository location: %v", err))
|
|
}
|
|
u.Path = path.Join(u.Path, "refs/heads/", ref)
|
|
|
|
var client *http.Client
|
|
if consumerCerts {
|
|
if subs == nil {
|
|
subs, err = rhsm.LoadSystemSubscriptions()
|
|
if err != nil {
|
|
return "", NewResolveRefError("error adding rhsm certificates when resolving ref: %s", err)
|
|
}
|
|
if subs.Consumer == nil {
|
|
return "", NewResolveRefError("error adding rhsm certificates when resolving ref")
|
|
}
|
|
}
|
|
|
|
tlsConf := &tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
}
|
|
|
|
if ca != nil {
|
|
caCertPEM, err := os.ReadFile(*ca)
|
|
if err != nil {
|
|
return "", NewResolveRefError("error adding rhsm certificates when resolving ref: %s", err)
|
|
}
|
|
roots := x509.NewCertPool()
|
|
ok := roots.AppendCertsFromPEM(caCertPEM)
|
|
if !ok {
|
|
return "", NewResolveRefError("error adding rhsm certificates when resolving ref")
|
|
}
|
|
tlsConf.RootCAs = roots
|
|
}
|
|
|
|
cert, err := tls.LoadX509KeyPair(subs.Consumer.ConsumerCert, subs.Consumer.ConsumerKey)
|
|
if err != nil {
|
|
return "", NewResolveRefError("error adding rhsm certificates when resolving ref: %s", err)
|
|
}
|
|
tlsConf.Certificates = []tls.Certificate{cert}
|
|
|
|
client = &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: tlsConf,
|
|
},
|
|
Timeout: 300 * time.Second,
|
|
}
|
|
} else {
|
|
client = &http.Client{}
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
if err != nil {
|
|
return "", NewResolveRefError("error preparing ostree resolve request: %s", err)
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return "", NewResolveRefError(fmt.Sprintf("error sending request to ostree repository %q: %v", u.String(), err))
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", NewResolveRefError("ostree repository %q returned status: %s", u.String(), resp.Status)
|
|
}
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", NewResolveRefError(fmt.Sprintf("error reading response from ostree repository %q: %v", u.String(), err))
|
|
}
|
|
checksum := strings.TrimSpace(string(body))
|
|
// Check that this is at least a hex string.
|
|
_, err = hex.DecodeString(checksum)
|
|
if err != nil {
|
|
return "", NewResolveRefError("ostree repository %q returned invalid reference", u.String())
|
|
}
|
|
return checksum, nil
|
|
}
|
|
|
|
// Resolve the ostree source specification to a commit specification.
|
|
//
|
|
// If a URL is defined in the source specification, the checksum of the ref is
|
|
// resolved, otherwise the checksum is an empty string. Failure to resolve the
|
|
// checksum results in a ResolveRefError.
|
|
//
|
|
// If the ref is already a checksum (64 alphanumeric characters), it is not
|
|
// resolved or checked against the repository.
|
|
//
|
|
// If the ref is malformed, the function returns with a RefError.
|
|
func Resolve(source SourceSpec) (CommitSpec, error) {
|
|
commit := CommitSpec{
|
|
Ref: source.Ref,
|
|
URL: source.URL,
|
|
}
|
|
|
|
if source.RHSM {
|
|
commit.Secrets = "org.osbuild.rhsm.consumer"
|
|
}
|
|
|
|
if verifyChecksum(source.Ref) {
|
|
// the ref is a commit: return as is
|
|
commit.Checksum = source.Ref
|
|
return commit, nil
|
|
}
|
|
|
|
if !verifyRef(source.Ref) {
|
|
// the ref is not a commit and it's also an invalid ref
|
|
return CommitSpec{}, NewRefError("Invalid ostree ref or commit %q", source.Ref)
|
|
}
|
|
|
|
// URL set: Resolve checksum
|
|
if source.URL != "" {
|
|
// If a URL is specified, we need to fetch the commit at the URL.
|
|
checksum, err := ResolveRef(source.URL, source.Ref, source.RHSM, nil, nil)
|
|
if err != nil {
|
|
return CommitSpec{}, err // ResolveRefError
|
|
}
|
|
commit.Checksum = checksum
|
|
}
|
|
return commit, nil
|
|
}
|