talos/pkg/grpc/middleware/authz/injector.go
Andrey Smirnov 2b5204200a
feat: enable resource API in the maintenance mode
This basically provides `talosctl get --insecure` in maintenance mode.
Only non-sensitive resources are available (equivalent to having
`os:reader` role in the Talos client certificate).

Changes:

* refactored insecure/maintenance client setup in talosctl
* `LinkStatus` is no longer sensitive as it shows only Wireguard public
key, `LinkSpec` still contains private key for obvious reasons
* maintenance mode injects `os:reader` role implicitly

The motivation behind this PR is to deprecate networkd-era interfaces &
routes APIs which are being used in TUI installer, and we need a
replacement.

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
2021-09-22 21:36:34 +03:00

136 lines
3.7 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 authz
import (
"context"
"fmt"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/peer"
"github.com/talos-systems/talos/pkg/machinery/role"
)
// InjectorMode specifies how roles are extracted.
type InjectorMode int
const (
// Disabled is used when RBAC is disabled in the machine configuration. All roles are assumed.
Disabled InjectorMode = iota
// ReadOnly is used to inject only Reader role.
ReadOnly
// MetadataOnly is used internally. Checks only metadata.
MetadataOnly
// Enabled is used when RBAC is enabled in the machine configuration. Roles are extracted normally.
Enabled
)
// Injector sets roles to the context.
type Injector struct {
// Mode.
Mode InjectorMode
// Logger.
Logger func(format string, v ...interface{})
}
func (i *Injector) logf(format string, v ...interface{}) {
if i.Logger != nil {
i.Logger(format, v...)
}
}
// extractRoles returns roles extracted from the user's certificate (in case of the first apid instance),
// or from gRPC metadata (in case of subsequent apid instances, machined, or user with impersonator role).
//
//nolint:gocyclo
func (i *Injector) extractRoles(ctx context.Context) role.Set {
// sanity check
if _, ok := getFromContext(ctx); ok {
panic("roles should not be present in the context at this point")
}
switch i.Mode {
case Disabled:
i.logf("RBAC is disabled, injecting all roles")
return role.All
case ReadOnly:
return role.MakeSet(role.Reader)
case MetadataOnly:
roles, _ := getFromMetadata(ctx, i.logf)
return roles
case Enabled:
p, ok := peer.FromContext(ctx)
if !ok {
panic("can't get peer information")
}
tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo)
if !ok {
panic(fmt.Sprintf("expected credentials.TLSInfo, got %T", p.AuthInfo))
}
if len(tlsInfo.State.PeerCertificates) != 1 {
panic(fmt.Sprintf("expected one certificate, got %d", len(tlsInfo.State.PeerCertificates)))
}
strings := tlsInfo.State.PeerCertificates[0].Subject.Organization
// TODO validate cert.KeyUsage, cert.ExtKeyUsage, cert.Issuer.Organization, other fields there?
roles, unknownRoles := role.Parse(strings)
i.logf("parsed peer's certificate orgs %v as %v (unknownRoles = %v)", strings, roles.Strings(), unknownRoles)
// trust gRPC metadata from clients with impersonator role if present
// (including requests proxied from other apid instances)
if roles.Includes(role.Impersonator) {
metadataRoles, ok := getFromMetadata(ctx, i.logf)
if ok {
return metadataRoles
}
// that's a real user with impersonator role then
i.logf("no roles in metadadata, returning parsed roles")
}
return roles
}
panic("unreachable")
}
// UnaryInterceptor returns grpc UnaryServerInterceptor.
func (i *Injector) UnaryInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
ctx = ContextWithRoles(ctx, i.extractRoles(ctx))
return handler(ctx, req)
}
}
// StreamInterceptor returns grpc StreamServerInterceptor.
func (i *Injector) StreamInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := stream.Context()
ctx = ContextWithRoles(ctx, i.extractRoles(ctx))
wrapped := grpc_middleware.WrapServerStream(stream)
wrapped.WrappedContext = ctx
return handler(srv, wrapped)
}
}