初始提交:OCI 面板后端(含 GenAI 网关一期)
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
package oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/oracle/oci-go-sdk/v65/common"
|
||||
"github.com/oracle/oci-go-sdk/v65/identitydomains"
|
||||
)
|
||||
|
||||
// ociConsolePolicyID 是预置 sign-on 策略「Security Policy for OCI Console」的固定 ID。
|
||||
const ociConsolePolicyID = "OciConsolePolicy"
|
||||
|
||||
// scimPatchSchema 是 SCIM PatchOp 的 schema。
|
||||
const scimPatchSchema = "urn:ietf:params:scim:api:messages:2.0:PatchOp"
|
||||
|
||||
// SignOnRuleInfo 是 sign-on 策略中一条规则的关键字段。
|
||||
type SignOnRuleInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Sequence int `json:"sequence"`
|
||||
AuthenticationFactor string `json:"authenticationFactor"`
|
||||
ConditionAttribute string `json:"conditionAttribute,omitempty"`
|
||||
ConditionValue string `json:"conditionValue,omitempty"`
|
||||
BuiltIn bool `json:"builtIn"`
|
||||
}
|
||||
|
||||
// ListConsoleSignOnRules 实现 Client:按优先级列出 OCI Console sign-on 策略的规则。
|
||||
func (c *RealClient) ListConsoleSignOnRules(ctx context.Context, cred Credentials, region string) ([]SignOnRuleInfo, error) {
|
||||
dc, err := c.domainsClient(ctx, cred, region)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refs, err := consolePolicyRules(ctx, dc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rules := make([]SignOnRuleInfo, 0, len(refs))
|
||||
for _, ref := range refs {
|
||||
info, err := loadSignOnRule(ctx, dc, deref(ref.Value), ref.Sequence)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rules = append(rules, info)
|
||||
}
|
||||
sort.Slice(rules, func(i, j int) bool { return rules[i].Sequence < rules[j].Sequence })
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func consolePolicyRules(ctx context.Context, dc identitydomains.IdentityDomainsClient) ([]identitydomains.PolicyRules, error) {
|
||||
resp, err := dc.GetPolicy(ctx, identitydomains.GetPolicyRequest{
|
||||
PolicyId: common.String(ociConsolePolicyID),
|
||||
Attributes: common.String("id,name,rules"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get console sign-on policy: %w", err)
|
||||
}
|
||||
return resp.Rules, nil
|
||||
}
|
||||
|
||||
func loadSignOnRule(ctx context.Context, dc identitydomains.IdentityDomainsClient, ruleID string, seq *int) (SignOnRuleInfo, error) {
|
||||
resp, err := dc.GetRule(ctx, identitydomains.GetRuleRequest{RuleId: &ruleID})
|
||||
if err != nil {
|
||||
return SignOnRuleInfo{}, fmt.Errorf("get rule %s: %w", ruleID, err)
|
||||
}
|
||||
info := SignOnRuleInfo{
|
||||
ID: ruleID,
|
||||
Name: deref(resp.Name),
|
||||
BuiltIn: strings.HasPrefix(ruleID, "OciConsole"),
|
||||
}
|
||||
if seq != nil {
|
||||
info.Sequence = *seq
|
||||
}
|
||||
for _, r := range resp.Return {
|
||||
if deref(r.Name) == "authenticationFactor" {
|
||||
info.AuthenticationFactor = deref(r.Value)
|
||||
}
|
||||
}
|
||||
fillRuleCondition(ctx, dc, resp.Rule, &info)
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// fillRuleCondition 尽力回填规则条件(仅解析直接引用 Condition 的规则)。
|
||||
func fillRuleCondition(ctx context.Context, dc identitydomains.IdentityDomainsClient, rule identitydomains.Rule, info *SignOnRuleInfo) {
|
||||
cg := rule.ConditionGroup
|
||||
if cg == nil || cg.Type != identitydomains.RuleConditionGroupTypeCondition || cg.Value == nil {
|
||||
return
|
||||
}
|
||||
resp, err := dc.GetCondition(ctx, identitydomains.GetConditionRequest{ConditionId: cg.Value})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info.ConditionAttribute = deref(resp.AttributeName)
|
||||
info.ConditionValue = deref(resp.AttributeValue)
|
||||
}
|
||||
|
||||
// CreateMfaExemptionRule 实现 Client:为指定 IdP 创建免 MFA sign-on 规则并置顶。
|
||||
// 规则语义与控制台一致:subject.authenticatedBy in [idpID] → authenticationFactor=IDP。
|
||||
func (c *RealClient) CreateMfaExemptionRule(ctx context.Context, cred Credentials, region, idpID, ruleName string) (SignOnRuleInfo, error) {
|
||||
dc, err := c.domainsClient(ctx, cred, region)
|
||||
if err != nil {
|
||||
return SignOnRuleInfo{}, err
|
||||
}
|
||||
condID, err := createIdpCondition(ctx, dc, idpID, ruleName)
|
||||
if err != nil {
|
||||
return SignOnRuleInfo{}, err
|
||||
}
|
||||
ruleID, err := createExemptionRule(ctx, dc, condID, ruleName)
|
||||
if err != nil {
|
||||
deleteConditionQuiet(ctx, dc, condID)
|
||||
return SignOnRuleInfo{}, err
|
||||
}
|
||||
if err := prependRuleToPolicy(ctx, dc, ruleID); err != nil {
|
||||
deleteRuleQuiet(ctx, dc, ruleID)
|
||||
deleteConditionQuiet(ctx, dc, condID)
|
||||
return SignOnRuleInfo{}, err
|
||||
}
|
||||
return SignOnRuleInfo{
|
||||
ID: ruleID, Name: ruleName, Sequence: 1,
|
||||
AuthenticationFactor: "IDP",
|
||||
ConditionAttribute: "subject.authenticatedBy",
|
||||
ConditionValue: jsonList(idpID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func jsonList(items ...string) string {
|
||||
b, _ := json.Marshal(items)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func createIdpCondition(ctx context.Context, dc identitydomains.IdentityDomainsClient, idpID, name string) (string, error) {
|
||||
value := jsonList(idpID)
|
||||
resp, err := dc.CreateCondition(ctx, identitydomains.CreateConditionRequest{
|
||||
Condition: identitydomains.Condition{
|
||||
Schemas: []string{"urn:ietf:params:scim:schemas:oracle:idcs:Condition"},
|
||||
Name: common.String(name + "-condition"),
|
||||
AttributeName: common.String("subject.authenticatedBy"),
|
||||
Operator: identitydomains.ConditionOperatorIn,
|
||||
AttributeValue: &value,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create idp condition: %w", err)
|
||||
}
|
||||
return deref(resp.Id), nil
|
||||
}
|
||||
|
||||
func createExemptionRule(ctx context.Context, dc identitydomains.IdentityDomainsClient, condID, name string) (string, error) {
|
||||
resp, err := dc.CreateRule(ctx, identitydomains.CreateRuleRequest{
|
||||
Rule: identitydomains.Rule{
|
||||
Schemas: []string{"urn:ietf:params:scim:schemas:oracle:idcs:Rule"},
|
||||
Name: &name,
|
||||
PolicyType: &identitydomains.RulePolicyType{Value: common.String("SignOn")},
|
||||
ConditionGroup: &identitydomains.RuleConditionGroup{
|
||||
Type: identitydomains.RuleConditionGroupTypeCondition,
|
||||
Value: &condID,
|
||||
},
|
||||
Return: []identitydomains.RuleReturn{
|
||||
{Name: common.String("effect"), Value: common.String("ALLOW")},
|
||||
{Name: common.String("reAuthenticate"), Value: common.String("false")},
|
||||
{Name: common.String("authenticationFactor"), Value: common.String("IDP")},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create exemption rule: %w", err)
|
||||
}
|
||||
return deref(resp.Id), nil
|
||||
}
|
||||
|
||||
// prependRuleToPolicy 把规则插到策略首位,其余规则优先级顺延。
|
||||
func prependRuleToPolicy(ctx context.Context, dc identitydomains.IdentityDomainsClient, ruleID string) error {
|
||||
existing, err := consolePolicyRules(ctx, dc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rules := []interface{}{map[string]interface{}{"value": ruleID, "sequence": 1}}
|
||||
for i, r := range sortedRules(existing) {
|
||||
rules = append(rules, map[string]interface{}{"value": deref(r.Value), "sequence": i + 2})
|
||||
}
|
||||
return patchPolicyRules(ctx, dc, rules)
|
||||
}
|
||||
|
||||
// removeRuleFromPolicy 从策略移除规则,其余规则优先级从 1 重排。
|
||||
func removeRuleFromPolicy(ctx context.Context, dc identitydomains.IdentityDomainsClient, ruleID string) error {
|
||||
existing, err := consolePolicyRules(ctx, dc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rules := make([]interface{}, 0, len(existing))
|
||||
for _, r := range sortedRules(existing) {
|
||||
if deref(r.Value) == ruleID {
|
||||
continue
|
||||
}
|
||||
rules = append(rules, map[string]interface{}{"value": deref(r.Value), "sequence": len(rules) + 1})
|
||||
}
|
||||
if len(rules) == len(existing) {
|
||||
return nil
|
||||
}
|
||||
return patchPolicyRules(ctx, dc, rules)
|
||||
}
|
||||
|
||||
func sortedRules(rules []identitydomains.PolicyRules) []identitydomains.PolicyRules {
|
||||
out := append([]identitydomains.PolicyRules(nil), rules...)
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
si, sj := 0, 0
|
||||
if out[i].Sequence != nil {
|
||||
si = *out[i].Sequence
|
||||
}
|
||||
if out[j].Sequence != nil {
|
||||
sj = *out[j].Sequence
|
||||
}
|
||||
return si < sj
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
func patchPolicyRules(ctx context.Context, dc identitydomains.IdentityDomainsClient, rules []interface{}) error {
|
||||
var value interface{} = rules
|
||||
_, err := dc.PatchPolicy(ctx, identitydomains.PatchPolicyRequest{
|
||||
PolicyId: common.String(ociConsolePolicyID),
|
||||
PatchOp: identitydomains.PatchOp{
|
||||
Schemas: []string{scimPatchSchema},
|
||||
Operations: []identitydomains.Operations{{
|
||||
Op: identitydomains.OperationsOpReplace,
|
||||
Path: common.String("rules"),
|
||||
Value: &value,
|
||||
}},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("patch console sign-on policy rules: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteMfaExemptionRule 实现 Client:删除免 MFA 规则并连带清理其条件。
|
||||
// 拒绝删除 Oracle 预置规则。
|
||||
func (c *RealClient) DeleteMfaExemptionRule(ctx context.Context, cred Credentials, region, ruleID string) error {
|
||||
if strings.HasPrefix(ruleID, "OciConsole") {
|
||||
return fmt.Errorf("delete sign-on rule: %s is a built-in rule and cannot be deleted", ruleID)
|
||||
}
|
||||
dc, err := c.domainsClient(ctx, cred, region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rule, err := dc.GetRule(ctx, identitydomains.GetRuleRequest{RuleId: &ruleID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get rule %s: %w", ruleID, err)
|
||||
}
|
||||
if err := removeRuleFromPolicy(ctx, dc, ruleID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := dc.DeleteRule(ctx, identitydomains.DeleteRuleRequest{RuleId: &ruleID}); err != nil {
|
||||
return fmt.Errorf("delete rule %s: %w", ruleID, err)
|
||||
}
|
||||
cg := rule.ConditionGroup
|
||||
if cg != nil && cg.Type == identitydomains.RuleConditionGroupTypeCondition && cg.Value != nil {
|
||||
deleteConditionQuiet(ctx, dc, *cg.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteRuleQuiet(ctx context.Context, dc identitydomains.IdentityDomainsClient, ruleID string) {
|
||||
_, _ = dc.DeleteRule(ctx, identitydomains.DeleteRuleRequest{RuleId: &ruleID})
|
||||
}
|
||||
|
||||
func deleteConditionQuiet(ctx context.Context, dc identitydomains.IdentityDomainsClient, condID string) {
|
||||
_, _ = dc.DeleteCondition(ctx, identitydomains.DeleteConditionRequest{ConditionId: &condID})
|
||||
}
|
||||
Reference in New Issue
Block a user