Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add security plugin #507

Merged
merged 21 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ec17b0d
makefile: add cluster.get-cert to copy the admin cert out of the cont…
Jakob3xD Apr 2, 2024
c23d80b
github/workflows: use cluster-get-cert as it is needed for security t…
Jakob3xD Apr 2, 2024
cb0cdf8
opensearch: add ToPointer function so it can be reused by plugins
Jakob3xD Apr 3, 2024
12adecf
golangci-lint: update config to expluce plugin security from dupl checks
Jakob3xD Apr 3, 2024
3ccfa3c
plugins/security: add base
Jakob3xD Apr 3, 2024
fdc277e
plugins/security: add account functions
Jakob3xD Apr 3, 2024
1f72680
plugins/security: add tenants functions
Jakob3xD Apr 3, 2024
9c17ba4
plugins/security: add ssl functions
Jakob3xD Apr 3, 2024
76a484c
plugins/security: add securityconfig functions
Jakob3xD Apr 3, 2024
9c14ea2
plugins/security: add rolesmapping functions
Jakob3xD Apr 3, 2024
3ad3396
plugins/security: add roles functions
Jakob3xD Apr 3, 2024
905dec7
plugins/security: add nodesdn functions
Jakob3xD Apr 3, 2024
2c3d765
plugins/security: add internalusers functions
Jakob3xD Apr 3, 2024
46dbdcc
plugins/security: add health functions
Jakob3xD Apr 3, 2024
27e261c
plugins/security: add flushcache functions
Jakob3xD Apr 3, 2024
5245ae5
plugins/security: add audit functions
Jakob3xD Apr 3, 2024
31fd63e
plugins/security: add actiongroups functions
Jakob3xD Apr 3, 2024
cf59416
add changelog
Jakob3xD Apr 3, 2024
13c10f5
ci/opensearch: adjust healthcheck to use admin cert instread of user
Jakob3xD Apr 4, 2024
5d2d63d
ci/opensearch: set opensearch security settings for testing
Jakob3xD Apr 5, 2024
289159b
github/workflows: get integration coverage from secure test
Jakob3xD Apr 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .ci/opensearch/Dockerfile.opensearch
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
ARG OPENSEARCH_VERSION
FROM opensearchproject/opensearch:${OPENSEARCH_VERSION}

ARG OPENSEARCH_VERSION
ARG opensearch_path=/usr/share/opensearch
ARG SECURE_INTEGRATION
ENV SECURE_INTEGRATION=$SECURE_INTEGRATION
ARG OPENSEARCH_INITIAL_ADMIN_PASSWORD

# Starting in 2.12.0 security demo requires an initial admin password, which is set as myStrongPassword123!
# Some opensearch secuirty settings are only present since 2.8.0 and causes older versions to brake if the setting is present
# https://apple.stackexchange.com/a/123408/11374
RUN if [ "$SECURE_INTEGRATION" != "true" ] ; then \
$opensearch_path/bin/opensearch-plugin remove opensearch-security; \
else \
$opensearch_path/opensearch-onetime-setup.sh; \
echo "plugins.security.nodes_dn_dynamic_config_enabled: true" | tee -a $opensearch_path/config/opensearch.yml > /dev/null; \
echo "plugins.security.unsupported.restapi.allow_securityconfig_modification: true" | tee -a $opensearch_path/config/opensearch.yml > /dev/null; \
echo "plugins.security.ssl_cert_reload_enabled: true" | tee -a $opensearch_path/config/opensearch.yml > /dev/null; \
function version { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }; \
if [ $(version $OPENSEARCH_VERSION) -ge $(version "2.12.0") ] || [ $OPENSEARCH_VERSION == "latest" ]; then \
echo user admin:myStrongPassword123! > curl.conf ; \
else \
echo user admin:admin > curl.conf ; \
fi\
if [ $(version $OPENSEARCH_VERSION) -ge $(version "2.8.0") ] || [ $OPENSEARCH_VERSION == "latest" ]; then \
echo "plugins.security.restapi.admin.enabled: true" | tee -a $opensearch_path/config/opensearch.yml > /dev/null; \
fi \
fi

HEALTHCHECK --start-period=20s --interval=30s \
CMD curl -sf -retry 5 --max-time 5 --retry-delay 5 --retry-max-time 30 \
$(if $SECURE_INTEGRATION; then echo "-K curl.conf -k https://"; fi)"localhost:9200" \
$(if $SECURE_INTEGRATION; then echo "--cert config/kirk.pem --key config/kirk-key.pem -k https://"; fi)"localhost:9200" \
|| bash -c 'kill -s 15 -1 && (sleep 10; kill -s 9 -1)'
1 change: 1 addition & 0 deletions .ci/opensearch/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ services:
args:
- SECURE_INTEGRATION=${SECURE_INTEGRATION:-false}
- OPENSEARCH_VERSION=${OPENSEARCH_VERSION:-latest}
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=myStrongPassword123!
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
run: |
export OPENSEARCH_VERSION=${{ matrix.entry.opensearch_version }}
export SECURE_INTEGRATION=${{ matrix.secured }}
make test-integ race=true
make cluster.get-cert test-integ race=true

- name: Stop the OpenSearch cluster
run: |
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/test-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@ jobs:
for attempt in `seq 25`; do sleep 5; \
if curl -s localhost:9200; \
then echo '=====> ready'; break; fi; if [ $attempt == 25 ]; then exit 1; fi; echo '=====> waiting...'; done
- run: make test-integ race=true coverage=true
- uses: codecov/codecov-action@v4
with:
file: tmp/integ.cov
flags: integration
- run: make test-integ race=true

secured:
name: Tests against secure cluster
Expand All @@ -54,4 +50,8 @@ jobs:
for attempt in `seq 25`; do sleep 5; \
if curl -s -ku admin:myStrongPassword123! https://localhost:9200; \
then echo '=====> ready'; break; fi; if [ $attempt == 25 ]; then exit 1; fi; echo '=====> waiting...'; done
- run: make test-integ-secure
- run: make cluster.get-cert test-integ-secure race=true coverage=true
- uses: codecov/codecov-action@v4
with:
file: tmp/integ.cov
flags: integration
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,4 @@ issues:
path: opensearchtransport/opensearchtransport.go
- linters:
- dupl
path: (-params\.go|api_indices|api_dangling\.go|api_point_in_time\.go|rethrottle\.go|api_cat-.*\.go)
path: (-params\.go|api_indices|api_dangling\.go|api_point_in_time\.go|rethrottle\.go|api_cat-.*\.go|plugins/security/api_\w+.go|plugins/security/api_.*-patch.go)
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Adds new error types ([#512](https://github.com/opensearch-project/opensearch-go/pull/506))
- Adds handling of non json errors to ParseError ([#512](https://github.com/opensearch-project/opensearch-go/pull/506))
- Adds the `Failures` field to opensearchapi structs ([#510](https://github.com/opensearch-project/opensearch-go/pull/510))
- Adds the `Fields` field containing the document fields to the `SearchHit` struct. ([#508](https://github.com/opensearch-project/opensearch-go/pull/508))
- Adds security plugin ([#507](https://github.com/opensearch-project/opensearch-go/pull/507))
- Adds security settings to container for security testing ([#507](https://github.com/opensearch-project/opensearch-go/pull/507))
- Adds cluster.get-certs to copy admin certs out of the container ([#507](https://github.com/opensearch-project/opensearch-go/pull/507))

### Changed
- Uses docker compose v2 instead of v1 ([#506](https://github.com/opensearch-project/opensearch-go/pull/506))
Expand All @@ -21,6 +25,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Adjusts and extent opensearch tests for better coverage ([#517](https://github.com/opensearch-project/opensearch-go/pull/517))
- Bumps codecov action version to v4 ([#517](https://github.com/opensearch-project/opensearch-go/pull/517))
- Changes bulk error/reason field and some cat response fields to pointer as they can be nil ([#510](https://github.com/opensearch-project/opensearch-go/pull/510))
- Adjust workflows to work with security plugin ([#507](https://github.com/opensearch-project/opensearch-go/pull/507))

### Deprecated

Expand Down
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,16 @@ cluster.build:
docker compose --project-directory .ci/opensearch build;

cluster.start:
docker compose --project-directory .ci/opensearch up -d ;
docker compose --project-directory .ci/opensearch up -d;

cluster.stop:
docker compose --project-directory .ci/opensearch down ;
docker compose --project-directory .ci/opensearch down;

cluster.get-cert:
@if [[ -v SECURE_INTEGRATION ]] && [[ $$SECURE_INTEGRATION == "true" ]]; then \
docker cp $$(docker compose --project-directory .ci/opensearch ps --format '{{.Name}}'):/usr/share/opensearch/config/kirk.pem admin.pem && \
docker cp $$(docker compose --project-directory .ci/opensearch ps --format '{{.Name}}'):/usr/share/opensearch/config/kirk-key.pem admin.key; \
fi


cluster.clean: ## Remove unused Docker volumes and networks
Expand Down
5 changes: 5 additions & 0 deletions opensearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,8 @@ func addrsToURLs(addrs []string) ([]*url.URL, error) {

return urls, nil
}

// ToPointer converts any value to a pointer, mainly used for request parameters
func ToPointer[V any](value V) *V {
return &value
}
6 changes: 6 additions & 0 deletions opensearch_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,9 @@ func TestParseElasticsearchVersion(t *testing.T) {
})
}
}

func TestToPointer(t *testing.T) {
testPointer := ToPointer(true)
assert.NotNil(t, testPointer)
assert.True(t, *testPointer)
}
80 changes: 80 additions & 0 deletions plugins/security/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: Apache-2.0
//
// The OpenSearch Contributors require contributions made to
// this file be licensed under the Apache-2.0 license or a
// compatible open source license.

package security

import (
"context"
"fmt"

"github.com/opensearch-project/opensearch-go/v3"
)

// Config represents the client configuration
type Config struct {
Client opensearch.Config
}

// Client represents the security Client summarizing all API calls
type Client struct {
Client *opensearch.Client
Account accountClient
ActionGroups actiongroupsClient
Audit auditClient
InternalUsers internalusersClient
NodesDN nodesdnClient
Roles rolesClient
RolesMapping rolesmappingClient
SecurityConfig securityconfigClient
SSL sslClient
Tenants tenantsClient
}

// clientInit inits the Client with all sub clients
func clientInit(rootClient *opensearch.Client) *Client {
client := &Client{
Client: rootClient,
}
client.Account = accountClient{apiClient: client}
client.ActionGroups = actiongroupsClient{apiClient: client}
client.Audit = auditClient{apiClient: client}
client.InternalUsers = internalusersClient{apiClient: client}
client.NodesDN = nodesdnClient{apiClient: client}
client.Roles = rolesClient{apiClient: client}
client.RolesMapping = rolesmappingClient{apiClient: client}
client.SecurityConfig = securityconfigClient{apiClient: client}
client.SSL = sslClient{apiClient: client}
client.Tenants = tenantsClient{apiClient: client}
return client
}

// NewClient returns a security client
func NewClient(config Config) (*Client, error) {
rootClient, err := opensearch.NewClient(config.Client)
if err != nil {
return nil, err
}

return clientInit(rootClient), nil
}

// do calls the opensearch.Client.Do() and checks the response for errors
func (c *Client) do(ctx context.Context, req opensearch.Request, dataPointer any) (*opensearch.Response, error) {
resp, err := c.Client.Do(ctx, req, dataPointer)
if err != nil {
return nil, err
}

if resp.IsError() {
if dataPointer != nil {
return resp, opensearch.ParseError(resp)
} else {
return resp, fmt.Errorf("status: %s", resp.Status())
}
}

return resp, nil
}
48 changes: 48 additions & 0 deletions plugins/security/api_account-get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: Apache-2.0
//
// The OpenSearch Contributors require contributions made to
// this file be licensed under the Apache-2.0 license or a
// compatible open source license.

package security

import (
"net/http"

"github.com/opensearch-project/opensearch-go/v3"
)

// AccountGetReq represents possible options for the account get request
type AccountGetReq struct {
Header http.Header
}

// GetRequest returns the *http.Request that gets executed by the client
func (r AccountGetReq) GetRequest() (*http.Request, error) {
return opensearch.BuildRequest(
"GET",
"/_plugins/_security/api/account",
nil,
make(map[string]string),
r.Header,
)
}

// AccountGetResp represents the returned struct of the account get response
type AccountGetResp struct {
UserName string `json:"user_name"`
IsReserved bool `json:"is_reserved"`
IsHidden bool `json:"is_hidden"`
IsInternaluser bool `json:"is_internal_user"`
BackendRoles []string `json:"backend_roles"`
CustomAttributes []string `json:"custom_attribute_names"`
UserRequestedTenant *string `json:"user_requested_tenant"`
Tennants map[string]bool `json:"tenants"`
Roles []string `json:"roles"`
response *opensearch.Response
}

// Inspect returns the Inspect type containing the raw *opensearch.Reponse
func (r AccountGetResp) Inspect() Inspect {
return Inspect{Response: r.response}
}
56 changes: 56 additions & 0 deletions plugins/security/api_account-put.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: Apache-2.0
//
// The OpenSearch Contributors require contributions made to
// this file be licensed under the Apache-2.0 license or a
// compatible open source license.

package security

import (
"bytes"
"encoding/json"
"net/http"

"github.com/opensearch-project/opensearch-go/v3"
)

// AccountPutReq represents possible options for the account put request
type AccountPutReq struct {
Body AccountPutBody

Header http.Header
}

// GetRequest returns the *http.Request that gets executed by the client
func (r AccountPutReq) GetRequest() (*http.Request, error) {
body, err := json.Marshal(r.Body)
if err != nil {
return nil, err
}

return opensearch.BuildRequest(
"PUT",
"/_plugins/_security/api/account",
bytes.NewReader(body),
make(map[string]string),
r.Header,
)
}

// AccountPutBody reperensts the request body for AccountPutReq
type AccountPutBody struct {
CurrentPassword string `json:"current_password"`
Password string `json:"password"`
}

// AccountPutResp represents the returned struct of the account put response
type AccountPutResp struct {
Message string `json:"message"`
Status string `json:"status"`
response *opensearch.Response
}

// Inspect returns the Inspect type containing the raw *opensearch.Reponse
func (r AccountPutResp) Inspect() Inspect {
return Inspect{Response: r.response}
}
45 changes: 45 additions & 0 deletions plugins/security/api_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: Apache-2.0
//
// The OpenSearch Contributors require contributions made to
// this file be licensed under the Apache-2.0 license or a
// compatible open source license.

package security

import (
"context"
)

type accountClient struct {
apiClient *Client
}

// Get executes a get account request with the optional AccountGetReq
func (c accountClient) Get(ctx context.Context, req *AccountGetReq) (AccountGetResp, error) {
if req == nil {
req = &AccountGetReq{}
}

var (
data AccountGetResp
err error
)
if data.response, err = c.apiClient.do(ctx, req, &data); err != nil {
return data, err
}

return data, nil
}

// Put executes a put account request with the required AccountPutReq
func (c accountClient) Put(ctx context.Context, req AccountPutReq) (AccountPutResp, error) {
var (
data AccountPutResp
err error
)
if data.response, err = c.apiClient.do(ctx, req, &data); err != nil {
return data, err
}

return data, nil
}
Loading
Loading