mirror of
https://github.com/hanzoai/compute.git
synced 2026-10-30 14:50:41 +00:00
The client_credentials token visor presents to egress is the same identity it presents to any Hanzo service, so it lives beside the clients that use it. An empty audience mints an unscoped token. Co-authored-by: Hanzo Dev <[email protected]>
96 lines
3.5 KiB
Go
96 lines
3.5 KiB
Go
// Copyright 2023 Hanzo Industries Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/hanzoai/egress/spend"
|
|
|
|
"github.com/hanzoai/compute/conf"
|
|
"github.com/hanzoai/compute/service"
|
|
)
|
|
|
|
// carry sends every cloud call through hanzoai/egress, so this process stops
|
|
// holding cloud credentials.
|
|
//
|
|
// Unconfigured it does nothing and visor calls clouds directly with the tokens
|
|
// on its Provider rows, which is what it has always done and what a local or
|
|
// single-binary run wants. Configured, the tokens are egress's: reading this
|
|
// pod's environment, config or memory yields nothing that spends.
|
|
//
|
|
// What visor still holds is its OWN token, and that is the trade rather than an
|
|
// oversight. A stolen caller token buys metered calls through our meter — rate
|
|
// limited, attributed, audited, revocable in one place — instead of a vendor
|
|
// key that spends without limit, off our network, and takes a rotation across
|
|
// every cloud to undo.
|
|
func carry() error {
|
|
address := strings.TrimSpace(conf.GetConfigString("egressAddress"))
|
|
if address == "" {
|
|
return nil
|
|
}
|
|
// WHO IS CALLING IS ASKED OF IAM, not of a config value. visor already signs
|
|
// in with a clientId and clientSecret; egress verifies what those buy the
|
|
// same way every service verifies a caller — issuer, audience, JWKS. So
|
|
// there is no second credential to mint, paste or rotate, and the token
|
|
// expires on its own.
|
|
//
|
|
// Refusing to start is the point. An operator who set an address means the
|
|
// cloud keys to be gone from here, and coming up without an identity would
|
|
// serve every cloud call as a 401 whose obvious repair is to put the keys
|
|
// back.
|
|
id := strings.TrimSpace(conf.GetConfigString("clientId"))
|
|
secret := strings.TrimSpace(conf.GetConfigString("clientSecret"))
|
|
iam := strings.TrimSpace(conf.GetConfigString("iamEndpoint"))
|
|
if id == "" || secret == "" || iam == "" {
|
|
return fmt.Errorf("egressAddress is set but visor has no IAM identity "+
|
|
"(clientId=%q iamEndpoint=%q): it cannot say who it is to egress", id, iam)
|
|
}
|
|
who := service.NewIdentity(iam, id, secret, strings.TrimSpace(conf.GetConfigString("egressAudience")), nil)
|
|
network, address := dial(address)
|
|
service.RegisterCarrier(func(c service.Credential) (*http.Client, error) {
|
|
token, err := who.Token()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return spend.Client(spend.Config{
|
|
Network: network,
|
|
Address: address,
|
|
Token: token,
|
|
Provider: c.Provider,
|
|
Account: c.Name,
|
|
}), nil
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// dial splits an egress address into the network and the address, so one
|
|
// configuration value covers a socket on this host and a service across the
|
|
// network. Bare means tcp, which is the common case.
|
|
//
|
|
// tcp://egress.hanzo.ai:9653
|
|
// unix:///run/hanzo/egress.sock
|
|
// egress.hanzo.ai:9653
|
|
func dial(address string) (string, string) {
|
|
for _, network := range []string{"unix", "tcp"} {
|
|
if rest, ok := strings.CutPrefix(address, network+"://"); ok {
|
|
return network, rest
|
|
}
|
|
}
|
|
return "tcp", address
|
|
}
|