mirror of
https://github.com/hanzoai/runtime.git
synced 2026-10-30 18:23:13 +00:00
Every sandbox was created privileged under the daemon's default runtime, and the setting that would have changed it defaulted to an empty string that silently meant runc. For a service whose whole premise is running code somebody else wrote, that is not a boundary. An org sets its isolation and the create request carries it, so a sandbox records what it was made with: changing the policy moves new sandboxes and leaves running ones alone. gvisor is the default. firecracker is delivered through kata's shim, which is why it is one name rather than two. runc is refused unless the runner names that org, and an empty list names nobody. Absent means the default, unknown is a refusal, and a runtime the daemon does not have is a refusal. Nothing substitutes quietly. The runner took an organization in a field called UserId with a note beside it saying so. Deciding who may have runc through a field named after something else is not reviewable. Privileged is replaced by dropping everything and granting the eight the daemon uses. Nothing the sandbox does broke; what stopped working is mount, mknod, raw sockets, chroot, writing to proc, and seeing the host's disks. AUDIT_WRITE is among them because without it sudo works and complains every time. The creation histogram's lowest bucket was a tenth of a second, so the metric could not observe the claim the product makes about its own speed. It can now, and it carries the isolation, so comparing them is a query. Co-authored-by: Hanzo Dev <[email protected]>
276 lines
5.5 KiB
Go
276 lines
5.5 KiB
Go
// Copyright 2025 Hanzo Industries Inc.
|
|
// SPDX-License-Identifier: AGPL-3.0
|
|
|
|
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/hanzoai/runtime/apps/cli/cmd"
|
|
"github.com/hanzoai/runtime/apps/cli/internal"
|
|
)
|
|
|
|
type Config struct {
|
|
ActiveProfileId string `json:"activeProfile"`
|
|
Profiles []Profile `json:"profiles"`
|
|
}
|
|
|
|
type Profile struct {
|
|
Id string `json:"id"`
|
|
Name string `json:"name"`
|
|
Api ServerApi `json:"api"`
|
|
ActiveOrganizationId *string `json:"activeOrganizationId"`
|
|
}
|
|
|
|
type ServerApi struct {
|
|
Url string `json:"url"`
|
|
Key *string `json:"key"`
|
|
Token *Token `json:"token"`
|
|
}
|
|
|
|
type Token struct {
|
|
AccessToken string `json:"accessToken"`
|
|
RefreshToken string `json:"refreshToken"`
|
|
ExpiresAt time.Time `json:"expiresAt"`
|
|
}
|
|
|
|
func GetConfig() (*Config, error) {
|
|
configFilePath, err := getConfigPath()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = os.Stat(configFilePath)
|
|
if os.IsNotExist(err) {
|
|
// Setup autocompletion when adding initial config
|
|
_ = cmd.DetectShellAndSetupAutocompletion(cmd.AutoCompleteCmd.Root())
|
|
|
|
config := &Config{}
|
|
return config, config.Save()
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var c Config
|
|
configContent, err := os.ReadFile(configFilePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = json.Unmarshal(configContent, &c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &c, nil
|
|
}
|
|
|
|
var ErrNoProfilesFound = errors.New("no profiles found. Run `runtime login` to authenticate")
|
|
|
|
func (c *Config) GetActiveProfile() (Profile, error) {
|
|
if len(c.Profiles) == 0 {
|
|
return Profile{}, ErrNoProfilesFound
|
|
}
|
|
|
|
for _, profile := range c.Profiles {
|
|
if profile.Id == c.ActiveProfileId {
|
|
return profile, nil
|
|
}
|
|
}
|
|
|
|
return Profile{}, ErrNoActiveProfile
|
|
}
|
|
|
|
var ErrNoActiveProfile = errors.New("no active profile found. Run `runtime login` to authenticate")
|
|
var ErrNoActiveOrganization = errors.New("no active organization found. Run `runtime organization use` to select an organization")
|
|
|
|
func (c *Config) Save() error {
|
|
configFilePath, err := getConfigPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = os.MkdirAll(filepath.Dir(configFilePath), 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
configContent, err := json.MarshalIndent(c, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(configFilePath, configContent, 0644)
|
|
}
|
|
|
|
func (c *Config) AddProfile(profile Profile) error {
|
|
c.Profiles = append(c.Profiles, profile)
|
|
c.ActiveProfileId = profile.Id
|
|
|
|
return c.Save()
|
|
}
|
|
|
|
func (c *Config) EditProfile(profile Profile) error {
|
|
for i, p := range c.Profiles {
|
|
if p.Id == profile.Id {
|
|
c.Profiles[i] = profile
|
|
|
|
return c.Save()
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("profile with id %s not found", profile.Id)
|
|
}
|
|
|
|
func (c *Config) RemoveProfile(profileId string) error {
|
|
if c.ActiveProfileId == profileId {
|
|
return errors.New("cannot remove active profile")
|
|
}
|
|
|
|
var profiles []Profile
|
|
for _, profile := range c.Profiles {
|
|
if profile.Id != profileId {
|
|
profiles = append(profiles, profile)
|
|
}
|
|
}
|
|
|
|
c.Profiles = profiles
|
|
|
|
return c.Save()
|
|
}
|
|
|
|
func (c *Config) GetProfile(profileId string) (Profile, error) {
|
|
for _, profile := range c.Profiles {
|
|
if profile.Id == profileId {
|
|
return profile, nil
|
|
}
|
|
}
|
|
|
|
return Profile{}, errors.New("profile not found")
|
|
}
|
|
|
|
func getConfigPath() (string, error) {
|
|
configDir, err := GetConfigDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return filepath.Join(configDir, "config.json"), nil
|
|
}
|
|
|
|
func GetConfigDir() (string, error) {
|
|
runtimeConfigDir := os.Getenv("RUNTIME_CONFIG_DIR")
|
|
if runtimeConfigDir != "" {
|
|
return runtimeConfigDir, nil
|
|
}
|
|
|
|
userConfigDir, err := os.UserConfigDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return filepath.Join(userConfigDir, "runtime"), nil
|
|
}
|
|
|
|
func DeleteConfigDir() error {
|
|
configDir, err := GetConfigDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.RemoveAll(configDir)
|
|
}
|
|
|
|
func GetActiveOrganizationId() (string, error) {
|
|
c, err := GetConfig()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
activeProfile, err := c.GetActiveProfile()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if activeProfile.ActiveOrganizationId == nil {
|
|
return "", ErrNoActiveOrganization
|
|
}
|
|
|
|
return *activeProfile.ActiveOrganizationId, nil
|
|
}
|
|
|
|
func IsApiKeyAuth() bool {
|
|
c, err := GetConfig()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
activeProfile, err := c.GetActiveProfile()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return activeProfile.Api.Key != nil && activeProfile.Api.Token == nil
|
|
}
|
|
|
|
func GetAuth0Domain() string {
|
|
auth0Domain := os.Getenv("RUNTIME_AUTH0_DOMAIN")
|
|
if auth0Domain == "" {
|
|
auth0Domain = internal.Auth0Domain
|
|
}
|
|
|
|
return auth0Domain
|
|
}
|
|
|
|
func GetAuth0ClientId() string {
|
|
auth0ClientId := os.Getenv("RUNTIME_AUTH0_CLIENT_ID")
|
|
if auth0ClientId == "" {
|
|
auth0ClientId = internal.Auth0ClientId
|
|
}
|
|
|
|
return auth0ClientId
|
|
}
|
|
|
|
func GetAuth0ClientSecret() string {
|
|
auth0ClientSecret := os.Getenv("RUNTIME_AUTH0_CLIENT_SECRET")
|
|
if auth0ClientSecret == "" {
|
|
auth0ClientSecret = internal.Auth0ClientSecret
|
|
}
|
|
|
|
return auth0ClientSecret
|
|
}
|
|
|
|
func GetAuth0CallbackPort() string {
|
|
auth0CallbackPort := os.Getenv("RUNTIME_AUTH0_CALLBACK_PORT")
|
|
if auth0CallbackPort == "" {
|
|
auth0CallbackPort = internal.Auth0CallbackPort
|
|
}
|
|
|
|
return auth0CallbackPort
|
|
}
|
|
|
|
func GetAuth0Audience() string {
|
|
auth0Audience := os.Getenv("RUNTIME_AUTH0_AUDIENCE")
|
|
if auth0Audience == "" {
|
|
auth0Audience = internal.Auth0Audience
|
|
}
|
|
|
|
return auth0Audience
|
|
}
|
|
|
|
func GetRuntimeApiUrl() string {
|
|
runtimeApiUrl := os.Getenv("RUNTIME_API_URL")
|
|
if runtimeApiUrl == "" {
|
|
runtimeApiUrl = internal.RuntimeApiUrl
|
|
}
|
|
|
|
return runtimeApiUrl
|
|
}
|