mirror of
https://github.com/siderolabs/image-factory.git
synced 2026-05-05 12:26:17 +02:00
Move ownership/auth checks from scattered frontend handlers into schematic.Factory.Get, which now accepts an OwnershipChecker. This eliminates duplicated checkOwnership methods across http and spdx frontends and ensures anonymous callers cannot probe schematic existence when auth is enabled. Also guard PXE credential embedding behind AuthProvider != nil so credentials are never propagated when auth is disabled. Signed-off-by: Mateusz Urbanek <mateusz.urbanek@siderolabs.com>
160 lines
4.3 KiB
Go
160 lines
4.3 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
// Package schematic implements schematic factory: storing image schematics.
|
|
package schematic
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/siderolabs/gen/xerrors"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/siderolabs/image-factory/internal/schematic/storage"
|
|
"github.com/siderolabs/image-factory/pkg/schematic"
|
|
)
|
|
|
|
// OwnershipChecker resolves the authenticated user from a context.
|
|
type OwnershipChecker interface {
|
|
UsernameFromContext(ctx context.Context) (string, bool)
|
|
}
|
|
|
|
// Factory is the schematic factory.
|
|
type Factory struct {
|
|
storage storage.Storage
|
|
metricGet prometheus.Counter
|
|
metricCreate prometheus.Counter
|
|
metricDuplicate prometheus.Counter
|
|
logger *zap.Logger
|
|
options Options
|
|
}
|
|
|
|
// Options for the schematic factory.
|
|
type Options struct {
|
|
MetricsNamespace string
|
|
}
|
|
|
|
// NewFactory creates a new schematic factory.
|
|
func NewFactory(logger *zap.Logger, storage storage.Storage, options Options) *Factory {
|
|
return &Factory{
|
|
options: options,
|
|
storage: storage,
|
|
logger: logger.With(zap.String("factory", "schematic")),
|
|
|
|
metricGet: prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "image_factory_schematic_get_total",
|
|
Help: "Number of times schematics were retrieved.",
|
|
Namespace: options.MetricsNamespace,
|
|
}),
|
|
metricCreate: prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "image_factory_schematic_create_total",
|
|
Help: "Number of new schematics created.",
|
|
Namespace: options.MetricsNamespace,
|
|
}),
|
|
metricDuplicate: prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "image_factory_schematic_duplicate_create_total",
|
|
Help: "Number of new schematics which were created as duplicate.",
|
|
Namespace: options.MetricsNamespace,
|
|
}),
|
|
}
|
|
}
|
|
|
|
// Put stores the schematic.
|
|
//
|
|
// If the schematic already exists, Put does nothing.
|
|
func (s *Factory) Put(ctx context.Context, cfg *schematic.Schematic) (string, error) {
|
|
id, err := cfg.ID()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err = s.storage.Head(ctx, id); err == nil {
|
|
s.logger.Info("schematic already exists", zap.String("id", id))
|
|
|
|
s.metricDuplicate.Inc()
|
|
|
|
return id, nil
|
|
}
|
|
|
|
data, err := cfg.Marshal()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = s.storage.Put(ctx, id, data)
|
|
if err == nil {
|
|
s.metricCreate.Inc()
|
|
|
|
s.logger.Info("schematic created", zap.String("id", id), zap.Any("customization", cfg.Customization))
|
|
}
|
|
|
|
return id, err
|
|
}
|
|
|
|
// Get retrieves the stored schematic.
|
|
func (s *Factory) get(ctx context.Context, id string) (*schematic.Schematic, error) {
|
|
data, err := s.storage.Get(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s.metricGet.Inc()
|
|
|
|
return schematic.Unmarshal(data)
|
|
}
|
|
|
|
// Get retrieves the stored schematic and enforces ownership.
|
|
//
|
|
// If auth is non-nil and the caller is unauthenticated, RequiresAuthenticationTag is returned
|
|
// even when the schematic is not found, to avoid leaking schematic existence to anonymous callers.
|
|
func (s *Factory) Get(ctx context.Context, id string, auth OwnershipChecker) (*schematic.Schematic, error) {
|
|
sc, err := s.get(ctx, id)
|
|
if err != nil {
|
|
if auth != nil {
|
|
if _, ok := auth.UsernameFromContext(ctx); !ok {
|
|
return nil, xerrors.NewTagged[schematic.RequiresAuthenticationTag](err)
|
|
}
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
if sc.Owner == "" && auth == nil {
|
|
return sc, nil
|
|
}
|
|
|
|
if auth == nil {
|
|
return nil, xerrors.NewTagged[schematic.RequiresAuthenticationTag](errors.New("authentication required"))
|
|
}
|
|
|
|
username, ok := auth.UsernameFromContext(ctx)
|
|
if !ok {
|
|
return nil, xerrors.NewTagged[schematic.RequiresAuthenticationTag](errors.New("authentication required"))
|
|
}
|
|
|
|
if username != sc.Owner {
|
|
return nil, xerrors.NewTagged[schematic.ForbiddenTag](errors.New("access denied"))
|
|
}
|
|
|
|
return sc, nil
|
|
}
|
|
|
|
// Describe implements prom.Collector interface.
|
|
func (s *Factory) Describe(ch chan<- *prometheus.Desc) {
|
|
prometheus.DescribeByCollect(s, ch)
|
|
}
|
|
|
|
// Collect implements prom.Collector interface.
|
|
func (s *Factory) Collect(ch chan<- prometheus.Metric) {
|
|
s.metricCreate.Collect(ch)
|
|
s.metricGet.Collect(ch)
|
|
s.metricDuplicate.Collect(ch)
|
|
|
|
s.storage.Collect(ch)
|
|
}
|
|
|
|
var _ prometheus.Collector = &Factory{}
|