From d19ffb4eb942c3693a5e28da720df79be0aedc73 Mon Sep 17 00:00:00 2001 From: Jacob Kozol Date: Thu, 7 May 2020 20:41:41 +0200 Subject: [PATCH] rpmmd: add ssl fields to RepoConfig rpmmd now contains three repo config structs. RepoConfig is for use throughout osbuild-composer. repository is for reading from our internal json repository files before creating a RepoConfig. dnfRepoConfig is is for use within rpmmd and it matches what dnf-json expects from a repo. Repos now contain support for rhsm. In order to connect to rhel's cdn, dnf must pass an sslcacert, sslcakey, and sslclientcert. When a repo is used for fetching metadata or depsolving it is checked for rhsm secrets. If secrets are needed they are retrieved from the host system. Packages requiring rhsm are marked as requiring "org.osbuild.rhsm" secrets. --- internal/rpmmd/repository.go | 159 +++++++++++++++++++++++++++++++---- 1 file changed, 143 insertions(+), 16 deletions(-) diff --git a/internal/rpmmd/repository.go b/internal/rpmmd/repository.go index 11a840b9d..f22c7f36d 100644 --- a/internal/rpmmd/repository.go +++ b/internal/rpmmd/repository.go @@ -6,22 +6,48 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" "sort" + "strings" "time" "github.com/gobwas/glob" + "github.com/google/uuid" ) -type RepoConfig struct { +type repository struct { + Id string `json:"id"` + BaseURL string `json:"baseurl,omitempty"` + Metalink string `json:"metalink,omitempty"` + MirrorList string `json:"mirrorlist,omitempty"` + GPGKey string `json:"gpgkey,omitempty"` + RHSM bool `json:"rhsm,omitempty"` +} + +type dnfRepoConfig struct { Id string `json:"id"` BaseURL string `json:"baseurl,omitempty"` Metalink string `json:"metalink,omitempty"` MirrorList string `json:"mirrorlist,omitempty"` GPGKey string `json:"gpgkey,omitempty"` IgnoreSSL bool `json:"ignoressl"` + SSLCACert string `json:"sslcacert,omitempty"` + SSLClientKey string `json:"sslclientkey,omitempty"` + SSLClientCert string `json:"sslclientcert,omitempty"` MetadataExpire string `json:"metadata_expire,omitempty"` } +type RepoConfig struct { + Id string + BaseURL string + Metalink string + MirrorList string + GPGKey string + IgnoreSSL bool + MetadataExpire string + RHSM bool +} + type PackageList []Package type Package struct { @@ -72,6 +98,7 @@ type PackageSpec struct { Path string `json:"path,omitempty"` RemoteLocation string `json:"remote_location,omitempty"` Checksum string `json:"checksum,omitempty"` + Secrets string `json:"secrets,omitempty"` } type PackageSource struct { @@ -125,6 +152,35 @@ func (re *RepositoryError) Error() string { return re.msg } +type RHSMSecrets struct { + SSLCACert string `json:"sslcacert,omitempty"` + SSLClientKey string `json:"sslclientkey,omitempty"` + SSLClientCert string `json:"sslclientcert,omitempty"` +} + +var rhsmSecrets RHSMSecrets + +func getRHSMSecrets() (RHSMSecrets, error) { + if rhsmSecrets == (RHSMSecrets{}) { + keys, err := filepath.Glob("/etc/pki/entitlement/*-key.pem") + if err != nil { + return rhsmSecrets, &RepositoryError{fmt.Sprintf("unable to find client key in /etc/pki/entitlement/: %v", err)} + } + for _, key := range keys { + cert := strings.TrimSuffix(key, "-key.pem") + ".pem" + if _, err := os.Stat(cert); err == nil { + rhsmSecrets = RHSMSecrets{ + SSLCACert: "/etc/rhsm/ca/redhat-uep.pem", + SSLClientKey: key, + SSLClientCert: cert, + } + break + } + } + } + return rhsmSecrets, nil +} + func LoadRepositories(confPaths []string, distro string) (map[string][]RepoConfig, error) { var f *os.File var err error @@ -143,14 +199,29 @@ func LoadRepositories(confPaths []string, distro string) (map[string][]RepoConfi } defer f.Close() - var repos map[string][]RepoConfig + var reposMap map[string][]repository + repoConfigs := make(map[string][]RepoConfig) - err = json.NewDecoder(f).Decode(&repos) + err = json.NewDecoder(f).Decode(&reposMap) if err != nil { return nil, err } - return repos, nil + for arch, repos := range reposMap { + for _, repo := range repos { + config := RepoConfig{ + Id: repo.Id, + BaseURL: repo.BaseURL, + Metalink: repo.Metalink, + MirrorList: repo.MirrorList, + GPGKey: repo.GPGKey, + RHSM: repo.RHSM, + } + + repoConfigs[arch] = append(repoConfigs[arch], config) + } + } + return repoConfigs, nil } func runDNF(command string, arguments interface{}, result interface{}) error { @@ -223,18 +294,52 @@ func NewRPMMD(cacheDir string) RPMMD { } } +func (repo RepoConfig) toDNFRepoConfig() (dnfRepoConfig, error) { + id := uuid.New().String() + dnfRepo := dnfRepoConfig{ + Id: id, + BaseURL: repo.BaseURL, + Metalink: repo.Metalink, + MirrorList: repo.MirrorList, + GPGKey: repo.GPGKey, + IgnoreSSL: repo.IgnoreSSL, + MetadataExpire: repo.MetadataExpire, + } + if repo.RHSM { + secrets, err := getRHSMSecrets() + if err != nil { + return dnfRepoConfig{}, err + } + dnfRepo.SSLCACert = secrets.SSLCACert + dnfRepo.SSLClientKey = secrets.SSLClientKey + dnfRepo.SSLClientCert = secrets.SSLClientCert + } + return dnfRepo, nil +} + func (r *rpmmdImpl) FetchMetadata(repos []RepoConfig, modulePlatformID string, arch string) (PackageList, map[string]string, error) { + var dnfRepoConfigs []dnfRepoConfig + for _, repo := range repos { + dnfRepo, err := repo.toDNFRepoConfig() + if err != nil { + return nil, nil, err + } + dnfRepoConfigs = append(dnfRepoConfigs, dnfRepo) + } + var arguments = struct { - Repos []RepoConfig `json:"repos"` - CacheDir string `json:"cachedir"` - ModulePlatformID string `json:"module_platform_id"` - Arch string `json:"arch"` - }{repos, r.CacheDir, modulePlatformID, arch} + Repos []dnfRepoConfig `json:"repos"` + CacheDir string `json:"cachedir"` + ModulePlatformID string `json:"module_platform_id"` + Arch string `json:"arch"` + }{dnfRepoConfigs, r.CacheDir, modulePlatformID, arch} var reply struct { Checksums map[string]string `json:"checksums"` Packages PackageList `json:"packages"` } + err := runDNF("dump", arguments, &reply) + sort.Slice(reply.Packages, func(i, j int) bool { return reply.Packages[i].Name < reply.Packages[j].Name }) @@ -242,19 +347,41 @@ func (r *rpmmdImpl) FetchMetadata(repos []RepoConfig, modulePlatformID string, a } func (r *rpmmdImpl) Depsolve(specs, excludeSpecs []string, repos []RepoConfig, modulePlatformID, arch string) ([]PackageSpec, map[string]string, error) { + repoMap := make(map[string]RepoConfig) + var dnfRepoConfigs []dnfRepoConfig + + for _, repo := range repos { + id := repo.Id + repoMap[id] = repo + + dnfRepo, err := repo.toDNFRepoConfig() + if err != nil { + return nil, nil, err + } + dnfRepoConfigs = append(dnfRepoConfigs, dnfRepo) + } + var arguments = struct { - PackageSpecs []string `json:"package-specs"` - ExcludSpecs []string `json:"exclude-specs"` - Repos []RepoConfig `json:"repos"` - CacheDir string `json:"cachedir"` - ModulePlatformID string `json:"module_platform_id"` - Arch string `json:"arch"` - }{specs, excludeSpecs, repos, r.CacheDir, modulePlatformID, arch} + PackageSpecs []string `json:"package-specs"` + ExcludSpecs []string `json:"exclude-specs"` + Repos []dnfRepoConfig `json:"repos"` + CacheDir string `json:"cachedir"` + ModulePlatformID string `json:"module_platform_id"` + Arch string `json:"arch"` + }{specs, excludeSpecs, dnfRepoConfigs, r.CacheDir, modulePlatformID, arch} var reply struct { Checksums map[string]string `json:"checksums"` Dependencies []PackageSpec `json:"dependencies"` } err := runDNF("depsolve", arguments, &reply) + + for i, pack := range reply.Dependencies { + id := pack.RepoID + if repoMap[id].RHSM { + reply.Dependencies[i].Secrets = "org.osbuild.rhsm" + } + } + return reply.Dependencies, reply.Checksums, err }