All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.xpack.security.ingest.SetSecurityUserProcessor Maven / Gradle / Ivy

There is a newer version: 8.16.1
Show newest version
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0; you may not use this file except in compliance with the Elastic License
 * 2.0.
 */
package org.elasticsearch.xpack.security.ingest;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.ingest.AbstractProcessor;
import org.elasticsearch.ingest.IngestDocument;
import org.elasticsearch.ingest.Processor;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.SecurityContext;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.AuthenticationField;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.ApiKeyService;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;

import static org.elasticsearch.ingest.ConfigurationUtils.newConfigurationException;
import static org.elasticsearch.ingest.ConfigurationUtils.readOptionalList;
import static org.elasticsearch.ingest.ConfigurationUtils.readStringProperty;

/**
 * A processor that adds information of the current authenticated user to the document being ingested.
 */
public final class SetSecurityUserProcessor extends AbstractProcessor {

    public static final String TYPE = "set_security_user";

    private static final Logger logger = LogManager.getLogger(SetSecurityUserProcessor.class);

    private final SecurityContext securityContext;
    private final Settings settings;
    private final String field;
    private final Set properties;

    public SetSecurityUserProcessor(
        String tag,
        String description,
        SecurityContext securityContext,
        Settings settings,
        String field,
        Set properties
    ) {
        super(tag, description);
        this.securityContext = securityContext;
        this.settings = Objects.requireNonNull(settings, "settings object cannot be null");
        if (XPackSettings.SECURITY_ENABLED.get(settings) == false) {
            logger.warn(
                "Creating processor [{}] (tag [{}]) on field [{}] but authentication is not currently enabled on this cluster "
                    + " - this processor is likely to fail at runtime if it is used",
                TYPE,
                tag,
                field
            );
        } else if (this.securityContext == null) {
            throw new IllegalArgumentException("Authentication is allowed on this cluster state, but there is no security context");
        }
        this.field = field;
        this.properties = properties;
    }

    @Override
    public IngestDocument execute(IngestDocument ingestDocument) throws Exception {
        Authentication authentication = null;
        User user = null;
        if (this.securityContext != null) {
            authentication = securityContext.getAuthentication();
            if (authentication != null) {
                user = authentication.getEffectiveSubject().getUser();
            }
        }

        if (user == null) {
            logger.debug(
                "Failed to find active user. SecurityContext=[{}] Authentication=[{}] User=[{}]",
                securityContext,
                authentication,
                user
            );
            if (XPackSettings.SECURITY_ENABLED.get(settings)) {
                // This shouldn't happen. If authentication is allowed (and active), then there _should_ always be an authenticated user.
                // If we ever see this error message, then one of our assumptions are wrong.
                throw new IllegalStateException(
                    "There is no authenticated user - the [" + TYPE + "] processor requires an authenticated user"
                );
            } else {
                throw new IllegalStateException(
                    "Security (authentication) is not enabled on this cluster, so there is no active user - "
                        + "the ["
                        + TYPE
                        + "] processor cannot be used without security"
                );
            }
        }

        Object fieldValue = ingestDocument.getFieldValue(field, Object.class, true);

        @SuppressWarnings("unchecked")
        Map userObject = fieldValue instanceof Map ? (Map) fieldValue : new HashMap<>();

        for (Property property : properties) {
            switch (property) {
                case USERNAME:
                    if (user.principal() != null) {
                        userObject.put("username", user.principal());
                    }
                    break;
                case FULL_NAME:
                    if (user.fullName() != null) {
                        userObject.put("full_name", user.fullName());
                    }
                    break;
                case EMAIL:
                    if (user.email() != null) {
                        userObject.put("email", user.email());
                    }
                    break;
                case ROLES:
                    if (user.roles() != null && user.roles().length != 0) {
                        userObject.put("roles", Arrays.asList(user.roles()));
                    }
                    break;
                case METADATA:
                    if (user.metadata() != null && user.metadata().isEmpty() == false) {
                        userObject.put("metadata", user.metadata());
                    }
                    break;
                case API_KEY:
                    if (authentication.isApiKey()) {
                        final String apiKey = "api_key";
                        final Object existingApiKeyField = userObject.get(apiKey);
                        @SuppressWarnings("unchecked")
                        final Map apiKeyField = existingApiKeyField instanceof Map
                            ? (Map) existingApiKeyField
                            : new HashMap<>();
                        if (authentication.getAuthenticatingSubject().getMetadata().containsKey(AuthenticationField.API_KEY_NAME_KEY)) {
                            apiKeyField.put(
                                "name",
                                authentication.getAuthenticatingSubject().getMetadata().get(AuthenticationField.API_KEY_NAME_KEY)
                            );
                        }
                        if (authentication.getAuthenticatingSubject().getMetadata().containsKey(AuthenticationField.API_KEY_ID_KEY)) {
                            apiKeyField.put(
                                "id",
                                authentication.getAuthenticatingSubject().getMetadata().get(AuthenticationField.API_KEY_ID_KEY)
                            );
                        }
                        final Map apiKeyMetadata = ApiKeyService.getApiKeyMetadata(authentication);
                        if (false == apiKeyMetadata.isEmpty()) {
                            apiKeyField.put("metadata", apiKeyMetadata);
                        }
                        if (false == apiKeyField.isEmpty()) {
                            userObject.put(apiKey, apiKeyField);
                        }
                    }
                    break;
                case REALM:
                    final String realmKey = "realm";
                    final Object existingRealmField = userObject.get(realmKey);
                    @SuppressWarnings("unchecked")
                    final Map realmField = existingRealmField instanceof Map
                        ? (Map) existingRealmField
                        : new HashMap<>();

                    final Object realmName = ApiKeyService.getCreatorRealmName(authentication);
                    if (realmName != null) {
                        realmField.put("name", realmName);
                    }
                    final Object realmType = ApiKeyService.getCreatorRealmType(authentication);
                    if (realmType != null) {
                        realmField.put("type", realmType);
                    }
                    if (false == realmField.isEmpty()) {
                        userObject.put(realmKey, realmField);
                    }
                    break;
                case AUTHENTICATION_TYPE:
                    if (authentication.getAuthenticationType() != null) {
                        userObject.put("authentication_type", authentication.getAuthenticationType().toString());
                    }
                    break;
                default:
                    throw new UnsupportedOperationException("unsupported property [" + property + "]");
            }
        }
        ingestDocument.setFieldValue(field, userObject);
        return ingestDocument;
    }

    @Override
    public String getType() {
        return TYPE;
    }

    String getField() {
        return field;
    }

    Set getProperties() {
        return properties;
    }

    public static final class Factory implements Processor.Factory {

        private final Supplier securityContext;
        private final Settings settings;

        public Factory(Supplier securityContext, Settings settings) {
            this.securityContext = securityContext;
            this.settings = settings;
        }

        @Override
        public SetSecurityUserProcessor create(
            Map processorFactories,
            String tag,
            String description,
            Map config
        ) throws Exception {
            String field = readStringProperty(TYPE, tag, config, "field");
            List propertyNames = readOptionalList(TYPE, tag, config, "properties");
            Set properties;
            if (propertyNames != null) {
                properties = EnumSet.noneOf(Property.class);
                for (String propertyName : propertyNames) {
                    properties.add(Property.parse(tag, propertyName));
                }
            } else {
                properties = EnumSet.allOf(Property.class);
            }
            return new SetSecurityUserProcessor(tag, description, securityContext.get(), settings, field, properties);
        }
    }

    public enum Property {

        USERNAME,
        FULL_NAME,
        EMAIL,
        ROLES,
        METADATA,
        API_KEY,
        REALM,
        AUTHENTICATION_TYPE;

        static Property parse(String tag, String value) {
            try {
                return valueOf(value.toUpperCase(Locale.ROOT));
            } catch (IllegalArgumentException e) {
                // not using the original exception as its message is confusing
                // (e.g. 'No enum constant SetSecurityUserProcessor.Property.INVALID')
                throw newConfigurationException(TYPE, tag, "properties", "Property value [" + value + "] is in valid");
            }
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy