Add service-context login policy enforcement
Services send service_name and tags in POST /v1/auth/login. MCIAS evaluates auth:login policy with these as the resource context after credentials are verified, enabling rules like: deny guest/viewer human accounts from env:restricted services deny guest accounts from specific named services - loginRequest: add ServiceName and Tags fields - handleLogin: evaluate policy after credential+TOTP check; policy deny returns 403 (not 401) to distinguish access restriction from bad credentials - Go client: Options.ServiceName/Tags stored on Client, sent automatically in every Login() call - Python client: service_name/tags on __init__, sent in login() - Rust client: ClientOptions.service_name/tags, LoginRequest fields, Client stores and sends them in login() - openapi.yaml: document service_name/tags request fields and 403 response for policy-denied logins - engineering-standards.md: document service_name/tags in [mcias] config section with policy examples Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -185,16 +185,28 @@ type Options struct {
|
||||
CACertPath string
|
||||
// Token is an optional pre-existing bearer token.
|
||||
Token string
|
||||
// ServiceName is the name of this service as registered in MCIAS. It is
|
||||
// sent with every Login call so MCIAS can evaluate service-context policy
|
||||
// rules (e.g. deny guest users from logging into this service).
|
||||
// Populate from [mcias] service_name in the service's config file.
|
||||
ServiceName string
|
||||
// Tags are the service-level tags sent with every Login call. MCIAS
|
||||
// evaluates auth:login policy against these tags, enabling rules such as
|
||||
// "deny guest accounts from services tagged env:restricted".
|
||||
// Populate from [mcias] tags in the service's config file.
|
||||
Tags []string
|
||||
}
|
||||
|
||||
// Client is a thread-safe MCIAS REST API client.
|
||||
// Security: the bearer token is guarded by a sync.RWMutex; it is never
|
||||
// written to logs or included in error messages in this library.
|
||||
type Client struct {
|
||||
baseURL string
|
||||
http *http.Client
|
||||
mu sync.RWMutex
|
||||
token string
|
||||
baseURL string
|
||||
http *http.Client
|
||||
serviceName string
|
||||
tags []string
|
||||
mu sync.RWMutex
|
||||
token string
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -224,9 +236,11 @@ func New(serverURL string, opts Options) (*Client, error) {
|
||||
}
|
||||
transport := &http.Transport{TLSClientConfig: tlsCfg}
|
||||
c := &Client{
|
||||
baseURL: serverURL,
|
||||
http: &http.Client{Transport: transport},
|
||||
token: opts.Token,
|
||||
baseURL: serverURL,
|
||||
http: &http.Client{Transport: transport},
|
||||
token: opts.Token,
|
||||
serviceName: opts.ServiceName,
|
||||
tags: opts.Tags,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
@@ -343,16 +357,28 @@ func (c *Client) GetPublicKey() (*PublicKey, error) {
|
||||
// Login authenticates with username and password. On success the token is
|
||||
// stored in the Client and returned along with the expiry timestamp.
|
||||
// totpCode may be empty for accounts without TOTP.
|
||||
//
|
||||
// The client's ServiceName and Tags (from Options) are included in the
|
||||
// request so MCIAS can evaluate service-context policy rules.
|
||||
func (c *Client) Login(username, password, totpCode string) (token, expiresAt string, err error) {
|
||||
req := map[string]string{"username": username, "password": password}
|
||||
body := map[string]interface{}{
|
||||
"username": username,
|
||||
"password": password,
|
||||
}
|
||||
if totpCode != "" {
|
||||
req["totp_code"] = totpCode
|
||||
body["totp_code"] = totpCode
|
||||
}
|
||||
if c.serviceName != "" {
|
||||
body["service_name"] = c.serviceName
|
||||
}
|
||||
if len(c.tags) > 0 {
|
||||
body["tags"] = c.tags
|
||||
}
|
||||
var resp struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
}
|
||||
if err := c.do(http.MethodPost, "/v1/auth/login", req, &resp); err != nil {
|
||||
if err := c.do(http.MethodPost, "/v1/auth/login", body, &resp); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
c.setToken(resp.Token)
|
||||
|
||||
Reference in New Issue
Block a user