diff --git a/plugins/doc_fragments/login_options.py b/plugins/doc_fragments/login_options.py index 21483a87..252e4f3d 100644 --- a/plugins/doc_fragments/login_options.py +++ b/plugins/doc_fragments/login_options.py @@ -13,6 +13,7 @@ class ModuleDocFragment(object): choices: - '' - http_auth + - api_key default: '' auth_scheme: description: @@ -57,6 +58,14 @@ class ModuleDocFragment(object): required: no type: int default: 9200 + api_key_encoded: + description: + - API key credentials which is the Base64-encoding of the UTF-8\ + representation of the id and api_key joined by a colon (:). + - Supported from Elastic 8+. + - See [Create API Key](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html) documentation for specifics. + required: no + type: str timeout: description: - Response timeout in seconds. diff --git a/plugins/module_utils/elastic_common.py b/plugins/module_utils/elastic_common.py index 94236464..5379f9f3 100644 --- a/plugins/module_utils/elastic_common.py +++ b/plugins/module_utils/elastic_common.py @@ -4,6 +4,7 @@ import traceback + elastic_found = False E_IMP_ERR = None NotFoundError = None @@ -27,9 +28,10 @@ def elastic_common_argument_spec(): Returns a dict containing common options shared across the elastic modules """ options = dict( - auth_method=dict(type='str', choices=['', 'http_auth'], default=''), + auth_method=dict(type='str', choices=['', 'http_auth', 'api_key'], default=''), auth_scheme=dict(type='str', choices=['http', 'https'], default='http'), cafile=dict(type='str', default=None), + api_key_encoded=dict(type='str', default=None, no_log=True), connection_options=dict(type='list', elements='dict', default=[]), login_user=dict(type='str', required=False), login_password=dict(type='str', required=False, no_log=True), @@ -53,17 +55,27 @@ def build_auth(self, module): Build the auth list for elastic according to the passed in parameters ''' auth = {} - if module.params['auth_method'] != '': - if module.params['auth_method'] == 'http_auth': - auth["http_auth"] = (module.params['login_user'], - module.params['login_password']) - - if module.params['cafile'] is not None: - from ssl import create_default_context - context = create_default_context(module.params['cafile']) - auth["ssl_context"] = context - else: - module.fail_json("Invalid or unsupported auth_method provided") + if not module.params['auth_method']: + return auth + + if module.params['auth_method'] == 'http_auth': + # username/password authentication. + auth["http_auth"] = (module.params['login_user'], + module.params['login_password']) + elif module.params['auth_method'] == 'api_key': + # api key authentication. Won't work for v7 of the driver + # The api_key is actually the base64 encoded version of + # the id and api_key separated by a colon. + auth["api_key"] = module.params['api_key_encoded'] + else: + module.fail_json("Invalid or unsupported auth_method provided") + + # CA file has been provided. Add it to auth dict + if module.params['cafile'] is not None: + from ssl import create_default_context + context = create_default_context(module.params['cafile']) + auth["ssl_context"] = context + return auth def connect(self): diff --git a/tests/integration/targets/elastic_index/tasks/103.yml b/tests/integration/targets/elastic_index/tasks/103.yml new file mode 100644 index 00000000..de82e636 --- /dev/null +++ b/tests/integration/targets/elastic_index/tasks/103.yml @@ -0,0 +1,59 @@ +--- +- vars: + elastic_user: elastic + elastic_password: secret + elastic_port: 9200 + elastic_api_key_name: "test-api-key" + + block: + + - name: Get Elasticsearch version + ansible.builtin.uri: + url: http://localhost:9200 + method: GET + user: "{{ elastic_user }}" + password: "{{ elastic_password }}" + return_content: yes + headers: + Content-Type: "application/json" + register: es_version_response + + - name: Create an API key for Elasticsearch + ansible.builtin.uri: + url: "http://localhost:{{ elastic_port }}/_security/api_key" + method: POST + user: "{{ elastic_user }}" + password: "{{ elastic_password }}" + body_format: json + body: | + { + "name": "{{ elastic_api_key_name }}", + "expiration": "1d", + "role_descriptors": {} + } + headers: + Content-Type: "application/json" + return_content: yes + register: api_key_response + when: es_version_response.json.version.number[0] | int > 7 + + - assert: + that: + - api_key_response.json.name == "test-api-key" + when: es_version_response.json.version.number[0] | int > 7 + + - name: Create an index using the api key + community.elastic.elastic_index: + name: myapikeyindex + auth_method: "api_key" + api_key_encoded: "{{ api_key_response.json.encoded }}" + auth_scheme: "http" + check_mode: yes + register: result + when: es_version_response.json.version.number[0] | int > 7 + + - assert: + that: + - result.msg == "The index 'myapikeyindex' was created." + - result.changed == True + when: es_version_response.json.version.number[0] | int > 7 diff --git a/tests/integration/targets/elastic_index/tasks/main.yml b/tests/integration/targets/elastic_index/tasks/main.yml index 13ed0a2e..da6e2ecc 100644 --- a/tests/integration/targets/elastic_index/tasks/main.yml +++ b/tests/integration/targets/elastic_index/tasks/main.yml @@ -12,3 +12,5 @@ name: setup_elastic - import_tasks: 2-test-with-auth.yml + + - import_tasks: 103.yml diff --git a/tests/integration/targets/setup_elastic/docker/single-node-elastic-with-auth.yml b/tests/integration/targets/setup_elastic/docker/single-node-elastic-with-auth.yml index 0b67b449..45f17e75 100644 --- a/tests/integration/targets/setup_elastic/docker/single-node-elastic-with-auth.yml +++ b/tests/integration/targets/setup_elastic/docker/single-node-elastic-with-auth.yml @@ -11,6 +11,7 @@ services: - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - ELASTIC_PASSWORD=secret # password for default user: elastic - xpack.security.enabled=true + - xpack.security.authc.api_key.enabled=true ulimits: memlock: soft: -1