Skip to content

Commit

Permalink
feat(opensearch): implement support for opensearch acls (#316)
Browse files Browse the repository at this point in the history
  • Loading branch information
mortenlj authored Oct 18, 2023
1 parent 7ef85d1 commit cef6f95
Show file tree
Hide file tree
Showing 4 changed files with 619 additions and 31 deletions.
2 changes: 2 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ type Client struct {
OrganizationUserGroups *OrganizationUserGroupHandler
OrganizationUserGroupMembers *OrganizationUserGroupMembersHandler
OpenSearchSecurityPluginHandler *OpenSearchSecurityPluginHandler
OpenSearchACLs *OpenSearchACLsHandler
}

// GetUserAgentOrDefault configures a default userAgent value, if one has not been provided.
Expand Down Expand Up @@ -270,6 +271,7 @@ func (c *Client) Init() {
c.OrganizationUserGroups = &OrganizationUserGroupHandler{c}
c.OrganizationUserGroupMembers = &OrganizationUserGroupMembersHandler{c}
c.OpenSearchSecurityPluginHandler = &OpenSearchSecurityPluginHandler{c}
c.OpenSearchACLs = &OpenSearchACLsHandler{c}
}

func (c *Client) doGetRequest(ctx context.Context, endpoint string, req interface{}) ([]byte, error) {
Expand Down
92 changes: 61 additions & 31 deletions elasticsearch_acls.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ type (
)

// Update updates Elasticsearch ACL config
//
// Deprecated: Use OpenSearchACLsHandler.Update instead.
func (h *ElasticSearchACLsHandler) Update(ctx context.Context, project, service string, req ElasticsearchACLRequest) (*ElasticSearchACLResponse, error) {
path := buildPath("project", project, "service", service, "elasticsearch", "acl")
bts, err := h.client.doPutRequest(ctx, path, req)
Expand All @@ -50,12 +52,12 @@ func (h *ElasticSearchACLsHandler) Update(ctx context.Context, project, service
}

var r ElasticSearchACLResponse
errR := checkAPIResponse(bts, &r)

return &r, errR
return &r, checkAPIResponse(bts, &r)
}

// Get gets all existing Elasticsearch ACLs config
//
// Deprecated: Use OpenSearchACLsHandler.Get instead.
func (h *ElasticSearchACLsHandler) Get(ctx context.Context, project, service string) (*ElasticSearchACLResponse, error) {
path := buildPath("project", project, "service", service, "elasticsearch", "acl")
bts, err := h.client.doGetRequest(ctx, path, nil)
Expand All @@ -64,62 +66,90 @@ func (h *ElasticSearchACLsHandler) Get(ctx context.Context, project, service str
}

var r ElasticSearchACLResponse
errR := checkAPIResponse(bts, &r)

return &r, errR
return &r, checkAPIResponse(bts, &r)
}

// Delete subtracts ACL from already existing Elasticsearch ACLs config
// Delete removes the specified ACL from the existing ElasticSearch ACLs config.
//
// Deprecated: Use OpenSearchACLConfig.Delete instead.
func (conf *ElasticSearchACLConfig) Delete(ctx context.Context, acl ElasticSearchACL) *ElasticSearchACLConfig {
for p, existingAcl := range conf.ACLs { // subtract ALC from existing ACLs config entry that supposed to be deleted
newACLs := []ElasticSearchACL{} // Create a new slice to hold the updated list of ACLs.

// Iterate over each existing ACL entry.
for _, existingAcl := range conf.ACLs {
// If the ACL usernames match, we'll potentially modify the rules.
if acl.Username == existingAcl.Username {
for i := range existingAcl.Rules {
// remove ACL from existing ACLs list
for _, rule := range acl.Rules {
if existingAcl.Rules[i].Permission == rule.Permission && existingAcl.Rules[i].Index == rule.Index {
conf.ACLs[p].Rules = append(conf.ACLs[p].Rules[:i], conf.ACLs[p].Rules[i+1:]...)
newRules := []ElasticsearchACLRule{} // Create a new slice to hold the updated list of rules.

// Check each existing rule against the rules in the ACL to be deleted.
for _, existingRule := range existingAcl.Rules {
match := false // Flag to track if the existing rule matches any rule in the ACL to be deleted.
for _, ruleToDelete := range acl.Rules {
if existingRule.Permission == ruleToDelete.Permission && existingRule.Index == ruleToDelete.Index {
match = true // The existing rule matches a rule in the ACL to be deleted.
break
}
}

// delete ACL item from ACLs list is there are not rules attached to it
if len(conf.ACLs[p].Rules) == 0 {
conf.ACLs = append(conf.ACLs[:p], conf.ACLs[p+1:]...)
// If the existing rule doesn't match any rule in the ACL to be deleted, add it to the new list.
if !match {
newRules = append(newRules, existingRule)
}
}

// If there are remaining rules after deletion, add the modified ACL to the new list.
if len(newRules) > 0 {
existingAcl.Rules = newRules
newACLs = append(newACLs, existingAcl)
}
} else {
// If the usernames don't match, directly add the existing ACL to the new list.
newACLs = append(newACLs, existingAcl)
}
}

// Replace the original list of ACLs with the updated list.
conf.ACLs = newACLs
return conf
}

// Add appends new ACL to already existing Elasticsearch ACLs config
// Add appends new ACL to the existing ElasticSearch ACLs config.
//
// Deprecated: Use OpenSearchACLConfig.Add instead.
func (conf *ElasticSearchACLConfig) Add(acl ElasticSearchACL) *ElasticSearchACLConfig {
var userAlreadyExist bool
var userIndex int
userExists := false

// check what ACL rules we already have for a user, and if we find that rule already exists,
// remove it from a rules slice since there is no need of adding duplicates records to the ACL list
// Iterate over the existing ACLs to identify duplicates and determine user existence.
for p, existingAcl := range conf.ACLs {
if acl.Username == existingAcl.Username { // ACL record for this user already exists
userAlreadyExist = true
if acl.Username == existingAcl.Username {
userExists = true
userIndex = p
for _, existingRule := range existingAcl.Rules {
for i, rule := range acl.Rules {
if existingRule.Permission == rule.Permission && existingRule.Index == rule.Index {
// remove rule since it already exists for this user
acl.Rules = append(acl.Rules[:i], acl.Rules[i+1:]...)

// Filter out any rules in the ACL to add that already exist for the user.
remainingRules := []ElasticsearchACLRule{}
for _, rule := range acl.Rules {
exists := false
for _, existingRule := range existingAcl.Rules {
if rule.Permission == existingRule.Permission && rule.Index == existingRule.Index {
exists = true
break
}
}
if !exists {
remainingRules = append(remainingRules, rule)
}
}
acl.Rules = remainingRules
}
}

// If no rules remain for the user, return the existing configuration.
if len(acl.Rules) == 0 {
return conf // nothing to add to already existing ACL rules list for a user
return conf
}

// add to existing Elasticsearch ACL config new records
if userAlreadyExist {
// Add the new or updated ACL to the config.
if userExists {
conf.ACLs[userIndex].Rules = append(conf.ACLs[userIndex].Rules, acl.Rules...)
} else {
conf.ACLs = append(conf.ACLs, acl)
Expand Down
151 changes: 151 additions & 0 deletions opensearch_acls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package aiven

import "context"

type (
// OpenSearchACLsHandler Aiven go-client handler for OpenSearch ACLs
OpenSearchACLsHandler struct {
client *Client
}

// OpenSearchACLRequest Aiven API request
// https://api.aiven.io/v1/project/<project>/service/<service_name>/opensearch/acl
OpenSearchACLRequest struct {
OpenSearchACLConfig OpenSearchACLConfig `json:"opensearch_acl_config"`
}

// OpenSearchACLResponse Aiven API response
// https://api.aiven.io/v1/project/<project>/service/<service_name>/opensearch/acl
OpenSearchACLResponse struct {
APIResponse
OpenSearchACLConfig OpenSearchACLConfig `json:"opensearch_acl_config"`
}

// OpenSearchACLConfig represents a configuration for OpenSearch ACLs
OpenSearchACLConfig struct {
ACLs []OpenSearchACL `json:"acls"`
Enabled bool `json:"enabled"`
ExtendedAcl bool `json:"extendedAcl"`
}

// OpenSearchACL represents a OpenSearch ACLs entry
OpenSearchACL struct {
Rules []OpenSearchACLRule `json:"rules"`
Username string `json:"username"`
}

// OpenSearchACLRule represents a OpenSearch ACLs Rule entry
OpenSearchACLRule struct {
Index string `json:"index"`
Permission string `json:"permission"`
}
)

// Update updates OpenSearch ACL config
func (h *OpenSearchACLsHandler) Update(ctx context.Context, project, service string, req OpenSearchACLRequest) (*OpenSearchACLResponse, error) {
path := buildPath("project", project, "service", service, "opensearch", "acl")
bts, err := h.client.doPutRequest(ctx, path, req)
if err != nil {
return nil, err
}

var r OpenSearchACLResponse
return &r, checkAPIResponse(bts, &r)
}

// Get gets all existing OpenSearch ACLs config
func (h *OpenSearchACLsHandler) Get(ctx context.Context, project, service string) (*OpenSearchACLResponse, error) {
path := buildPath("project", project, "service", service, "opensearch", "acl")
bts, err := h.client.doGetRequest(ctx, path, nil)
if err != nil {
return nil, err
}

var r OpenSearchACLResponse
return &r, checkAPIResponse(bts, &r)
}

// Delete removes the specified ACL from the existing OpenSearch ACLs config.
func (conf *OpenSearchACLConfig) Delete(ctx context.Context, acl OpenSearchACL) *OpenSearchACLConfig {
newACLs := []OpenSearchACL{} // Create a new slice to hold the updated list of ACLs.

// Iterate over each existing ACL entry.
for _, existingAcl := range conf.ACLs {
// If the ACL usernames match, we'll potentially modify the rules.
if acl.Username == existingAcl.Username {
newRules := []OpenSearchACLRule{} // Create a new slice to hold the updated list of rules.

// Check each existing rule against the rules in the ACL to be deleted.
for _, existingRule := range existingAcl.Rules {
match := false // Flag to track if the existing rule matches any rule in the ACL to be deleted.
for _, ruleToDelete := range acl.Rules {
if existingRule.Permission == ruleToDelete.Permission && existingRule.Index == ruleToDelete.Index {
match = true // The existing rule matches a rule in the ACL to be deleted.
break
}
}
// If the existing rule doesn't match any rule in the ACL to be deleted, add it to the new list.
if !match {
newRules = append(newRules, existingRule)
}
}

// If there are remaining rules after deletion, add the modified ACL to the new list.
if len(newRules) > 0 {
existingAcl.Rules = newRules
newACLs = append(newACLs, existingAcl)
}
} else {
// If the usernames don't match, directly add the existing ACL to the new list.
newACLs = append(newACLs, existingAcl)
}
}

// Replace the original list of ACLs with the updated list.
conf.ACLs = newACLs
return conf
}

// Add appends new ACL to the existing OpenSearch ACLs config.
func (conf *OpenSearchACLConfig) Add(acl OpenSearchACL) *OpenSearchACLConfig {
var userIndex int
userExists := false

// Iterate over the existing ACLs to identify duplicates and determine user existence.
for p, existingAcl := range conf.ACLs {
if acl.Username == existingAcl.Username {
userExists = true
userIndex = p

// Filter out any rules in the ACL to add that already exist for the user.
remainingRules := []OpenSearchACLRule{}
for _, rule := range acl.Rules {
exists := false
for _, existingRule := range existingAcl.Rules {
if rule.Permission == existingRule.Permission && rule.Index == existingRule.Index {
exists = true
break
}
}
if !exists {
remainingRules = append(remainingRules, rule)
}
}
acl.Rules = remainingRules
}
}

// If no rules remain for the user, return the existing configuration.
if len(acl.Rules) == 0 {
return conf
}

// Add the new or updated ACL to the config.
if userExists {
conf.ACLs[userIndex].Rules = append(conf.ACLs[userIndex].Rules, acl.Rules...)
} else {
conf.ACLs = append(conf.ACLs, acl)
}

return conf
}
Loading

0 comments on commit cef6f95

Please sign in to comment.