mirror of
https://github.com/lightninglabs/aperture.git
synced 2026-01-31 15:14:26 +01:00
mint: introduce proper LSAT creation and verification
This package adheres to the agreed upon internal design document of the macaroon portion of an LSAT. It is able to mint LSATs for a set of services at any tier, each containing their desired set of constraints. LSAT verification so far only ensures the that token was minted by us and that the target service attempted to be accessed is authorized according to the white-listed services contained in the token.
This commit is contained in:
256
mint/mint.go
Normal file
256
mint/mint.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package mint
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrSecretNotFound is an error returned when we attempt to retrieve a
|
||||
// secret by its key but it is not found.
|
||||
ErrSecretNotFound = errors.New("secret not found")
|
||||
)
|
||||
|
||||
// Challenger is an interface used to present requesters of LSATs with a
|
||||
// challenge that must be satisfied before an LSAT can be validated. This
|
||||
// challenge takes the form of a Lightning payment request.
|
||||
type Challenger interface {
|
||||
// NewChallenge returns a new challenge in the form of a Lightning
|
||||
// payment request. The payment hash is also returned as a convenience
|
||||
// to avoid having to decode the payment request in order to retrieve
|
||||
// its payment hash.
|
||||
NewChallenge() (string, lntypes.Hash, error)
|
||||
}
|
||||
|
||||
// SecretStore is the store responsible for storing LSAT secrets. These secrets
|
||||
// are required for proper verification of each minted LSAT.
|
||||
type SecretStore interface {
|
||||
// NewSecret creates a new cryptographically random secret which is
|
||||
// keyed by the given hash.
|
||||
NewSecret(context.Context, [sha256.Size]byte) ([lsat.SecretSize]byte, error)
|
||||
|
||||
// GetSecret returns the cryptographically random secret that
|
||||
// corresponds to the given hash. If there is no secret, then
|
||||
// ErrSecretNotFound is returned.
|
||||
GetSecret(context.Context, [sha256.Size]byte) ([lsat.SecretSize]byte, error)
|
||||
|
||||
// RevokeSecret removes the cryptographically random secret that
|
||||
// corresponds to the given hash. This acts as a NOP if the secret does
|
||||
// not exist.
|
||||
RevokeSecret(context.Context, [sha256.Size]byte) error
|
||||
}
|
||||
|
||||
// ServiceLimiter abstracts the source of caveats that should be applied to an
|
||||
// LSAT for a particular service.
|
||||
type ServiceLimiter interface {
|
||||
// ServiceCapabilities returns the capabilities caveats for each
|
||||
// service. This determines which capabilities of each service can be
|
||||
// accessed.
|
||||
ServiceCapabilities(context.Context, ...lsat.Service) ([]lsat.Caveat, error)
|
||||
|
||||
// ServiceConstraints returns the constraints for each service. This
|
||||
// enforces additional constraints on a particular service/service
|
||||
// capability.
|
||||
ServiceConstraints(context.Context, ...lsat.Service) ([]lsat.Caveat, error)
|
||||
}
|
||||
|
||||
// Config packages all of the required dependencies to instantiate a new LSAT
|
||||
// mint.
|
||||
type Config struct {
|
||||
// Secrets is our source for LSAT secrets which will be used for
|
||||
// verification purposes.
|
||||
Secrets SecretStore
|
||||
|
||||
// Challenger is our source of new challenges to present requesters of
|
||||
// an LSAT with.
|
||||
Challenger Challenger
|
||||
|
||||
// ServiceLimiter provides us with how we should limit a new LSAT based
|
||||
// on its target services.
|
||||
ServiceLimiter ServiceLimiter
|
||||
}
|
||||
|
||||
// Mint is an entity that is able to mint and verify LSATs for a set of
|
||||
// services.
|
||||
type Mint struct {
|
||||
cfg Config
|
||||
}
|
||||
|
||||
// New creates a new LSAT mint backed by its given dependencies.
|
||||
func New(cfg *Config) *Mint {
|
||||
return &Mint{cfg: *cfg}
|
||||
}
|
||||
|
||||
// MintLSAT mints a new LSAT for the target services.
|
||||
func (m *Mint) MintLSAT(ctx context.Context,
|
||||
services ...lsat.Service) (*macaroon.Macaroon, string, error) {
|
||||
|
||||
// We'll start by retrieving a new challenge in the form of a Lightning
|
||||
// payment request to present the requester of the LSAT with.
|
||||
paymentRequest, paymentHash, err := m.cfg.Challenger.NewChallenge()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// TODO(wilmer): remove invoice if any of the operations below fail?
|
||||
|
||||
// We can then proceed to mint the LSAT with a unique identifier that is
|
||||
// mapped to a unique secret.
|
||||
id, err := createUniqueIdentifier(paymentHash)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
idHash := sha256.Sum256(id)
|
||||
secret, err := m.cfg.Secrets.NewSecret(ctx, idHash)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
macaroon, err := macaroon.New(
|
||||
secret[:], id, "lsat", macaroon.LatestVersion,
|
||||
)
|
||||
if err != nil {
|
||||
// Attempt to revoke the secret to save space.
|
||||
_ = m.cfg.Secrets.RevokeSecret(ctx, idHash)
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Include any restrictions that should be immediately applied to the
|
||||
// LSAT.
|
||||
var caveats []lsat.Caveat
|
||||
if len(services) > 0 {
|
||||
var err error
|
||||
caveats, err = m.caveatsForServices(ctx, services...)
|
||||
if err != nil {
|
||||
// Attempt to revoke the secret to save space.
|
||||
_ = m.cfg.Secrets.RevokeSecret(ctx, idHash)
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
if err := lsat.AddFirstPartyCaveats(macaroon, caveats...); err != nil {
|
||||
// Attempt to revoke the secret to save space.
|
||||
_ = m.cfg.Secrets.RevokeSecret(ctx, idHash)
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return macaroon, paymentRequest, nil
|
||||
}
|
||||
|
||||
// createUniqueIdentifier creates a new LSAT identifier bound to a payment hash
|
||||
// and a randomly generated ID.
|
||||
func createUniqueIdentifier(paymentHash lntypes.Hash) ([]byte, error) {
|
||||
tokenID, err := generateTokenID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := &lsat.Identifier{
|
||||
Version: lsat.LatestVersion,
|
||||
PaymentHash: paymentHash,
|
||||
TokenID: tokenID,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := lsat.EncodeIdentifier(&buf, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// generateTokenID generates a new random LSAT ID.
|
||||
func generateTokenID() ([lsat.TokenIDSize]byte, error) {
|
||||
var tokenID [lsat.TokenIDSize]byte
|
||||
_, err := rand.Read(tokenID[:])
|
||||
return tokenID, err
|
||||
}
|
||||
|
||||
// caveatsForServices returns all of the caveats that should be applied to an
|
||||
// LSAT for the target services.
|
||||
func (m *Mint) caveatsForServices(ctx context.Context,
|
||||
services ...lsat.Service) ([]lsat.Caveat, error) {
|
||||
|
||||
servicesCaveat, err := lsat.NewServicesCaveat(services...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
capabilities, err := m.cfg.ServiceLimiter.ServiceCapabilities(
|
||||
ctx, services...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
constraints, err := m.cfg.ServiceLimiter.ServiceConstraints(
|
||||
ctx, services...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
caveats := []lsat.Caveat{servicesCaveat}
|
||||
caveats = append(caveats, capabilities...)
|
||||
caveats = append(caveats, constraints...)
|
||||
return caveats, nil
|
||||
}
|
||||
|
||||
// VerificationParams holds all of the requirements to properly verify an LSAT.
|
||||
type VerificationParams struct {
|
||||
// Macaroon is the macaroon as part of the LSAT we'll attempt to verify.
|
||||
Macaroon *macaroon.Macaroon
|
||||
|
||||
// Preimage is the preimage that should correspond to the LSAT's payment
|
||||
// hash.
|
||||
Preimage lntypes.Preimage
|
||||
|
||||
// TargetService is the target service a user of an LSAT is attempting
|
||||
// to access.
|
||||
TargetService string
|
||||
}
|
||||
|
||||
// VerifyLSAT attempts to verify an LSAT with the given parameters.
|
||||
func (m *Mint) VerifyLSAT(ctx context.Context, params *VerificationParams) error {
|
||||
// We'll first perform a quick check to determine if a valid preimage
|
||||
// was provided.
|
||||
id, err := lsat.DecodeIdentifier(bytes.NewReader(params.Macaroon.Id()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if params.Preimage.Hash() != id.PaymentHash {
|
||||
return fmt.Errorf("invalid preimage %v for %v", params.Preimage,
|
||||
id.PaymentHash)
|
||||
}
|
||||
|
||||
// If there was, then we'll ensure the LSAT was minted by us.
|
||||
secret, err := m.cfg.Secrets.GetSecret(
|
||||
ctx, sha256.Sum256(params.Macaroon.Id()),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawCaveats, err := params.Macaroon.VerifySignature(secret[:], nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// With the LSAT verified, we'll now inspect its caveats to ensure the
|
||||
// target service is authorized.
|
||||
var caveats []lsat.Caveat
|
||||
for _, rawCaveat := range rawCaveats {
|
||||
// LSATs can contain third-party caveats that we're not aware
|
||||
// of, so just skip those.
|
||||
caveat, err := lsat.DecodeCaveat(rawCaveat)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
caveats = append(caveats, caveat)
|
||||
}
|
||||
return lsat.VerifyCaveats(
|
||||
caveats, lsat.NewServicesSatisfier(params.TargetService),
|
||||
)
|
||||
}
|
||||
227
mint/mint_test.go
Normal file
227
mint/mint_test.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package mint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
testService = lsat.Service{
|
||||
Name: "lightning_loop",
|
||||
Tier: lsat.BaseTier,
|
||||
}
|
||||
)
|
||||
|
||||
// TestBasicLSAT ensures that an LSAT can only access the services it's
|
||||
// authorized to.
|
||||
func TestBasicLSAT(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
mint := New(&Config{
|
||||
Secrets: newMockSecretStore(),
|
||||
Challenger: newMockChallenger(),
|
||||
ServiceLimiter: newMockServiceLimiter(),
|
||||
})
|
||||
|
||||
// Mint a basic LSAT which is only able to access the given service.
|
||||
macaroon, _, err := mint.MintLSAT(ctx, testService)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mint LSAT: %v", err)
|
||||
}
|
||||
|
||||
params := VerificationParams{
|
||||
Macaroon: macaroon,
|
||||
Preimage: testPreimage,
|
||||
TargetService: testService.Name,
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, ¶ms); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
|
||||
// It should not be able to access an unknown service.
|
||||
unknownParams := params
|
||||
unknownParams.TargetService = "uknown"
|
||||
err = mint.VerifyLSAT(ctx, &unknownParams)
|
||||
if !strings.Contains(err.Error(), "not authorized") {
|
||||
t.Fatal("expected LSAT to not be authorized")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdminLSAT ensures that an admin LSAT (one without a services caveat) is
|
||||
// authorized to access any service.
|
||||
func TestAdminLSAT(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
mint := New(&Config{
|
||||
Secrets: newMockSecretStore(),
|
||||
Challenger: newMockChallenger(),
|
||||
ServiceLimiter: newMockServiceLimiter(),
|
||||
})
|
||||
|
||||
// Mint an admin LSAT by not including any services.
|
||||
macaroon, _, err := mint.MintLSAT(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mint LSAT: %v", err)
|
||||
}
|
||||
|
||||
// It should be able to access any service as it doesn't have a services
|
||||
// caveat.
|
||||
params := &VerificationParams{
|
||||
Macaroon: macaroon,
|
||||
Preimage: testPreimage,
|
||||
TargetService: testService.Name,
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, params); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRevokedLSAT ensures that we can no longer verify a revoked LSAT.
|
||||
func TestRevokedLSAT(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
mint := New(&Config{
|
||||
Secrets: newMockSecretStore(),
|
||||
Challenger: newMockChallenger(),
|
||||
ServiceLimiter: newMockServiceLimiter(),
|
||||
})
|
||||
|
||||
// Mint an LSAT and verify it.
|
||||
lsat, _, err := mint.MintLSAT(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mint LSAT: %v", err)
|
||||
}
|
||||
params := &VerificationParams{
|
||||
Macaroon: lsat,
|
||||
Preimage: testPreimage,
|
||||
TargetService: testService.Name,
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, params); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
|
||||
// Proceed to revoke it. We should no longer be able to verify it after.
|
||||
idHash := sha256.Sum256(lsat.Id())
|
||||
if err := mint.cfg.Secrets.RevokeSecret(ctx, idHash); err != nil {
|
||||
t.Fatalf("unable to revoke LSAT: %v", err)
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, params); err != ErrSecretNotFound {
|
||||
t.Fatalf("expected ErrSecretNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTamperedLSAT ensures that an LSAT that has been tampered with by
|
||||
// modifying its signature results in its verification failing.
|
||||
func TestTamperedLSAT(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
mint := New(&Config{
|
||||
Secrets: newMockSecretStore(),
|
||||
Challenger: newMockChallenger(),
|
||||
ServiceLimiter: newMockServiceLimiter(),
|
||||
})
|
||||
|
||||
// Mint a new LSAT and verify it is valid.
|
||||
mac, _, err := mint.MintLSAT(ctx, testService)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mint LSAT: %v", err)
|
||||
}
|
||||
params := VerificationParams{
|
||||
Macaroon: mac,
|
||||
Preimage: testPreimage,
|
||||
TargetService: testService.Name,
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, ¶ms); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
|
||||
// Create a tampered LSAT from the valid one.
|
||||
macBytes, err := mac.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to serialize macaroon: %v", err)
|
||||
}
|
||||
macBytes[len(macBytes)-1] = 0x00
|
||||
var tampered macaroon.Macaroon
|
||||
if err := tampered.UnmarshalBinary(macBytes); err != nil {
|
||||
t.Fatalf("unable to deserialize macaroon: %v", err)
|
||||
}
|
||||
|
||||
// Attempting to verify the tampered LSAT should fail.
|
||||
tamperedParams := params
|
||||
tamperedParams.Macaroon = &tampered
|
||||
err = mint.VerifyLSAT(ctx, &tamperedParams)
|
||||
if !strings.Contains(err.Error(), "signature mismatch") {
|
||||
t.Fatal("expected tampered LSAT to be invalid")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDemotedServicesLSAT ensures that an LSAT which originally was authorized
|
||||
// to access a service, but was then demoted to no longer be the case, is no
|
||||
// longer authorized.
|
||||
func TestDemotedServicesLSAT(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
mint := New(&Config{
|
||||
Secrets: newMockSecretStore(),
|
||||
Challenger: newMockChallenger(),
|
||||
ServiceLimiter: newMockServiceLimiter(),
|
||||
})
|
||||
|
||||
unauthorizedService := testService
|
||||
unauthorizedService.Name = "unauthorized"
|
||||
|
||||
// Mint an LSAT that is able to access two services, one of which will
|
||||
// be denied later on.
|
||||
mac, _, err := mint.MintLSAT(ctx, testService, unauthorizedService)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mint LSAT: %v", err)
|
||||
}
|
||||
|
||||
// It should be able to access both services.
|
||||
authorizedParams := VerificationParams{
|
||||
Macaroon: mac,
|
||||
Preimage: testPreimage,
|
||||
TargetService: testService.Name,
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, &authorizedParams); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
unauthorizedParams := VerificationParams{
|
||||
Macaroon: mac,
|
||||
Preimage: testPreimage,
|
||||
TargetService: unauthorizedService.Name,
|
||||
}
|
||||
if err := mint.VerifyLSAT(ctx, &unauthorizedParams); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
|
||||
// Demote the second service by including an additional services caveat
|
||||
// that only includes the first service.
|
||||
services, err := lsat.NewServicesCaveat(testService)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create services caveat: %v", err)
|
||||
}
|
||||
err = lsat.AddFirstPartyCaveats(mac, services)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to demote LSAT: %v", err)
|
||||
}
|
||||
|
||||
// It should now only be able to access the first, but not the second.
|
||||
if err := mint.VerifyLSAT(ctx, &authorizedParams); err != nil {
|
||||
t.Fatalf("unable to verify LSAT: %v", err)
|
||||
}
|
||||
err = mint.VerifyLSAT(ctx, &unauthorizedParams)
|
||||
if !strings.Contains(err.Error(), "not authorized") {
|
||||
t.Fatal("expected macaroon to be invalid")
|
||||
}
|
||||
}
|
||||
113
mint/mock_test.go
Normal file
113
mint/mock_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package mint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"math/rand"
|
||||
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
var (
|
||||
testPreimage = lntypes.Preimage{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
||||
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
}
|
||||
testHash = testPreimage.Hash()
|
||||
testPayReq = "lnsb1..."
|
||||
)
|
||||
|
||||
type mockChallenger struct{}
|
||||
|
||||
var _ Challenger = (*mockChallenger)(nil)
|
||||
|
||||
func newMockChallenger() *mockChallenger {
|
||||
return &mockChallenger{}
|
||||
}
|
||||
|
||||
func (d *mockChallenger) NewChallenge() (string, lntypes.Hash, error) {
|
||||
return testPayReq, testHash, nil
|
||||
}
|
||||
|
||||
type mockSecretStore struct {
|
||||
secrets map[[sha256.Size]byte][lsat.SecretSize]byte
|
||||
}
|
||||
|
||||
var _ SecretStore = (*mockSecretStore)(nil)
|
||||
|
||||
func (s *mockSecretStore) NewSecret(ctx context.Context,
|
||||
id [sha256.Size]byte) ([lsat.SecretSize]byte, error) {
|
||||
|
||||
var secret [lsat.SecretSize]byte
|
||||
if _, err := rand.Read(secret[:]); err != nil {
|
||||
return secret, err
|
||||
}
|
||||
s.secrets[id] = secret
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func (s *mockSecretStore) GetSecret(ctx context.Context,
|
||||
id [sha256.Size]byte) ([lsat.SecretSize]byte, error) {
|
||||
|
||||
secret, ok := s.secrets[id]
|
||||
if !ok {
|
||||
return secret, ErrSecretNotFound
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func (s *mockSecretStore) RevokeSecret(ctx context.Context,
|
||||
id [sha256.Size]byte) error {
|
||||
|
||||
delete(s.secrets, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMockSecretStore() *mockSecretStore {
|
||||
return &mockSecretStore{
|
||||
secrets: make(map[[sha256.Size]byte][lsat.SecretSize]byte),
|
||||
}
|
||||
}
|
||||
|
||||
type mockServiceLimiter struct {
|
||||
capabilities map[lsat.Service]lsat.Caveat
|
||||
constraints map[lsat.Service][]lsat.Caveat
|
||||
}
|
||||
|
||||
var _ ServiceLimiter = (*mockServiceLimiter)(nil)
|
||||
|
||||
func newMockServiceLimiter() *mockServiceLimiter {
|
||||
return &mockServiceLimiter{
|
||||
capabilities: make(map[lsat.Service]lsat.Caveat),
|
||||
constraints: make(map[lsat.Service][]lsat.Caveat),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *mockServiceLimiter) ServiceCapabilities(ctx context.Context,
|
||||
services ...lsat.Service) ([]lsat.Caveat, error) {
|
||||
|
||||
var res []lsat.Caveat
|
||||
for _, service := range services {
|
||||
capabilities, ok := l.capabilities[service]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
res = append(res, capabilities)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (l *mockServiceLimiter) ServiceConstraints(ctx context.Context,
|
||||
services ...lsat.Service) ([]lsat.Caveat, error) {
|
||||
|
||||
var res []lsat.Caveat
|
||||
for _, service := range services {
|
||||
constraints, ok := l.constraints[service]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
res = append(res, constraints...)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
Reference in New Issue
Block a user