debian-forge-composer/vendor/github.com/osbuild/images/pkg/rhsm/secrets.go
Sanne Raymaekers af97ca6fe7 go.mod: bump images to v0.144.0
tag v0.144.0
Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com>

Changes with 0.144.0

----------------
  * customizations: drop `FsNode` interface as it is not used (#1490)
    * Author: Michael Vogt, Reviewers: Brian C. Lane, Simon de Vlieger
  * distro/rhel10/ami: unset authselect in ImageConfig (COMPOSER-2517) (#1469)
    * Author: Achilleas Koutsou, Reviewers: Brian C. Lane, Michael Vogt
  * manifest/os: immediately create directories (HMS-6043) (#1502)
    * Author: Simon de Vlieger, Reviewers: Achilleas Koutsou, Michael Vogt
  * osbuild: Add config to InsightsClientConfigStageOptions (#1503)
    * Author: rverdile, Reviewers: Michael Vogt, Sanne Raymaekers
  * refactor: replace logrus with stdlib log package (#1481)
    * Author: Lukáš Zapletal, Reviewers: Michael Vogt, Simon de Vlieger

— Somewhere on the Internet, 2025-05-08
2025-05-12 14:06:21 +02:00

200 lines
5.2 KiB
Go

package rhsm
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/osbuild/images/pkg/olog"
"gopkg.in/ini.v1"
)
type subscription struct {
id string
baseurl string
sslCACert string
sslClientKey string
sslClientCert string
}
// Subscriptions encapsulates all available subscriptions from the
// host system.
type Subscriptions struct {
available []subscription
secrets *RHSMSecrets // secrets are used in there is no matching subscription
Consumer *ConsumerSecrets
}
// RHSMSecrets represents a set of CA certificate, client key, and
// client certificate for a specific repository.
type RHSMSecrets struct {
SSLCACert string
SSLClientKey string
SSLClientCert string
}
// These secrets are present on any subscribed system and uniquely identify the host
type ConsumerSecrets struct {
ConsumerKey string
ConsumerCert string
}
func getRHSMSecrets() (*RHSMSecrets, error) {
// search /etc first to allow container users to override the entitlements
globs := []string{
"/etc/pki/entitlement/*-key.pem", // for regular systems
"/run/secrets/etc-pki-entitlement/*-key.pem", // for podman containers
}
for _, glob := range globs {
keys, err := filepath.Glob(glob)
if err != nil {
return nil, err
}
for _, key := range keys {
cert := strings.TrimSuffix(key, "-key.pem") + ".pem"
if _, err := os.Stat(cert); err == nil {
return &RHSMSecrets{
SSLCACert: "/etc/rhsm/ca/redhat-uep.pem",
SSLClientKey: key,
SSLClientCert: cert,
}, nil
}
}
}
return nil, fmt.Errorf("no matching key and certificate pair")
}
func getListOfSubscriptions() ([]subscription, error) {
// This file has a standard syntax for yum repositories which is
// documented in `man yum.conf`. The same parsing mechanism could
// be used for any other repo file in /etc/yum.repos.d/.
availableSubscriptionsFile := "/etc/yum.repos.d/redhat.repo"
content, err := os.ReadFile(availableSubscriptionsFile)
if err != nil {
if pErr, ok := err.(*os.PathError); ok {
if pErr.Err.Error() == "no such file or directory" {
// The system is not subscribed
return nil, nil
}
}
return nil, fmt.Errorf("failed to open the file with subscriptions: %w", err)
}
subscriptions, err := parseRepoFile(content)
if err != nil {
return nil, fmt.Errorf("failed to parse the file with subscriptions: %w", err)
}
return subscriptions, nil
}
func getConsumerSecrets() (*ConsumerSecrets, error) {
res := ConsumerSecrets{
ConsumerKey: "/etc/pki/consumer/key.pem",
ConsumerCert: "/etc/pki/consumer/cert.pem",
}
if _, err := os.Stat(res.ConsumerKey); err != nil {
return nil, fmt.Errorf("no consumer key found")
}
if _, err := os.Stat(res.ConsumerCert); err != nil {
return nil, fmt.Errorf("no consumer cert found")
}
return &res, nil
}
// LoadSystemSubscriptions loads all the available subscriptions.
func LoadSystemSubscriptions() (*Subscriptions, error) {
consumerSecrets, err := getConsumerSecrets()
if err != nil {
// Consumer secrets are only needed when resolving
// ostree content (see commit 632f272)
olog.Printf("WARNING: Failed to load consumer certs: %v", err)
}
subscriptions, err1 := getListOfSubscriptions()
secrets, err2 := getRHSMSecrets()
if subscriptions == nil && secrets == nil {
// Neither works, return an error because at least one has to be available
if err1 != nil {
return nil, err1
}
if err2 != nil {
return nil, err2
}
return nil, fmt.Errorf("failed to load subscriptions")
}
return &Subscriptions{
available: subscriptions,
secrets: secrets,
Consumer: consumerSecrets,
}, nil
}
func parseRepoFile(content []byte) ([]subscription, error) {
cfg, err := ini.Load(content)
if err != nil {
return nil, err
}
subscriptions := make([]subscription, 0)
for _, section := range cfg.Sections() {
id := section.Name()
key, err := section.GetKey("baseurl")
if err != nil {
continue
}
baseurl := key.String()
key, err = section.GetKey("sslcacert")
if err != nil {
continue
}
sslcacert := key.String()
key, err = section.GetKey("sslclientkey")
if err != nil {
continue
}
sslclientkey := key.String()
key, err = section.GetKey("sslclientcert")
if err != nil {
continue
}
sslclientcert := key.String()
subscriptions = append(subscriptions, subscription{
id: id,
baseurl: baseurl,
sslCACert: sslcacert,
sslClientKey: sslclientkey,
sslClientCert: sslclientcert,
})
}
return subscriptions, nil
}
// GetSecretsForBaseurl queries the Subscriptions structure for a RHSMSecrets of a single repository.
func (s *Subscriptions) GetSecretsForBaseurl(baseurls []string, arch, releasever string) (*RHSMSecrets, error) {
for _, subs := range s.available {
for _, baseurl := range baseurls {
url := strings.Replace(subs.baseurl, "$basearch", arch, -1)
url = strings.Replace(url, "$releasever", releasever, -1)
if url == baseurl {
return &RHSMSecrets{
SSLCACert: subs.sslCACert,
SSLClientKey: subs.sslClientKey,
SSLClientCert: subs.sslClientCert,
}, nil
}
}
}
// If there is no matching URL, fall back to the global secrets
if s.secrets != nil {
return s.secrets, nil
}
return nil, fmt.Errorf("no such baseurl in the available subscriptions")
}