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

org.elasticsearch.xpack.security.authc.service.FileTokensTool 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.authc.service;

import joptsimple.OptionSet;
import joptsimple.OptionSpec;

import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.MultiCommand;
import org.elasticsearch.cli.ProcessInfo;
import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.cli.EnvironmentAwareCommand;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.Predicates;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.elasticsearch.xpack.core.security.support.Validation;
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken.ServiceAccountTokenId;
import org.elasticsearch.xpack.security.support.FileAttributesChecker;

import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Predicate;

class FileTokensTool extends MultiCommand {

    FileTokensTool() {
        super("Manages elasticsearch service account file-tokens");
        subcommands.put("create", newCreateFileTokenCommand());
        subcommands.put("delete", newDeleteFileTokenCommand());
        subcommands.put("list", newListFileTokenCommand());
    }

    protected CreateFileTokenCommand newCreateFileTokenCommand() {
        return new CreateFileTokenCommand();
    }

    protected DeleteFileTokenCommand newDeleteFileTokenCommand() {
        return new DeleteFileTokenCommand();
    }

    protected ListFileTokenCommand newListFileTokenCommand() {
        return new ListFileTokenCommand();
    }

    static class CreateFileTokenCommand extends EnvironmentAwareCommand {

        private final OptionSpec arguments;

        CreateFileTokenCommand() {
            super("Create a file token for specified service account and token name");
            this.arguments = parser.nonOptions("service-account-principal token-name");
        }

        @Override
        public void execute(Terminal terminal, OptionSet options, Environment env, ProcessInfo processInfo) throws Exception {
            final ServiceAccountTokenId accountTokenId = parsePrincipalAndTokenName(arguments.values(options), env.settings());
            final Hasher hasher = Hasher.resolve(XPackSettings.SERVICE_TOKEN_HASHING_ALGORITHM.get(env.settings()));
            final Path serviceTokensFile = FileServiceAccountTokenStore.resolveFile(env);

            FileAttributesChecker attributesChecker = new FileAttributesChecker(serviceTokensFile);
            final Map tokenHashes = new TreeMap<>(FileServiceAccountTokenStore.parseFile(serviceTokensFile, null));

            try (ServiceAccountToken token = ServiceAccountToken.newToken(accountTokenId.getAccountId(), accountTokenId.getTokenName())) {
                if (tokenHashes.containsKey(token.getQualifiedName())) {
                    throw new UserException(ExitCodes.CODE_ERROR, "Service token [" + token.getQualifiedName() + "] already exists");
                }
                tokenHashes.put(token.getQualifiedName(), hasher.hash(token.getSecret()));
                FileServiceAccountTokenStore.writeFile(serviceTokensFile, tokenHashes);
                terminal.println("SERVICE_TOKEN " + token.getQualifiedName() + " = " + token.asBearerString());
            }

            attributesChecker.check(terminal);
        }

    }

    static class DeleteFileTokenCommand extends EnvironmentAwareCommand {

        private final OptionSpec arguments;

        DeleteFileTokenCommand() {
            super("Remove a file token for specified service account and token name");
            this.arguments = parser.nonOptions("service-account-principal token-name");
        }

        @Override
        public void execute(Terminal terminal, OptionSet options, Environment env, ProcessInfo processInfo) throws Exception {
            final ServiceAccountTokenId accountTokenId = parsePrincipalAndTokenName(arguments.values(options), env.settings());
            final String qualifiedName = accountTokenId.getQualifiedName();
            final Path serviceTokensFile = FileServiceAccountTokenStore.resolveFile(env);

            FileAttributesChecker attributesChecker = new FileAttributesChecker(serviceTokensFile);
            final Map tokenHashes = new TreeMap<>(FileServiceAccountTokenStore.parseFile(serviceTokensFile, null));

            if (tokenHashes.remove(qualifiedName) == null) {
                throw new UserException(ExitCodes.CODE_ERROR, "Service token [" + qualifiedName + "] does not exist");
            } else {
                FileServiceAccountTokenStore.writeFile(serviceTokensFile, tokenHashes);
            }
            attributesChecker.check(terminal);
        }
    }

    static class ListFileTokenCommand extends EnvironmentAwareCommand {

        private final OptionSpec arguments;

        ListFileTokenCommand() {
            super("List file tokens for the specified service account");
            this.arguments = parser.nonOptions("service-account-principal");
        }

        @Override
        public void execute(Terminal terminal, OptionSet options, Environment env, ProcessInfo processInfo) throws Exception {
            final List args = arguments.values(options);
            if (args.size() > 1) {
                throw new UserException(
                    ExitCodes.USAGE,
                    "Expected at most one argument, service-account-principal, found extra: ["
                        + Strings.collectionToCommaDelimitedString(args)
                        + "]"
                );
            }
            Predicate filter = Predicates.always();
            if (args.size() == 1) {
                final String principal = args.get(0);
                if (false == ServiceAccountService.isServiceAccountPrincipal(principal)) {
                    throw new UserException(
                        ExitCodes.NO_USER,
                        "Unknown service account principal: ["
                            + principal
                            + "]. Must be one of ["
                            + Strings.collectionToDelimitedString(ServiceAccountService.getServiceAccountPrincipals(), ",")
                            + "]"
                    );
                }
                filter = filter.and(k -> k.startsWith(principal + "/"));
            }
            final Path serviceTokensFile = FileServiceAccountTokenStore.resolveFile(env);
            final Map tokenHashes = new TreeMap<>(FileServiceAccountTokenStore.parseFile(serviceTokensFile, null));
            for (String key : tokenHashes.keySet()) {
                if (filter.test(key)) {
                    terminal.println(key);
                }
            }
        }
    }

    static ServiceAccountTokenId parsePrincipalAndTokenName(List arguments, Settings settings) throws UserException {
        if (arguments.isEmpty()) {
            throw new UserException(ExitCodes.USAGE, "Missing service-account-principal and token-name arguments");
        } else if (arguments.size() == 1) {
            throw new UserException(ExitCodes.USAGE, "Missing token-name argument");
        } else if (arguments.size() > 2) {
            throw new UserException(
                ExitCodes.USAGE,
                "Expected two arguments, service-account-principal and token-name, found extra: ["
                    + Strings.collectionToCommaDelimitedString(arguments)
                    + "]"
            );
        }
        final String principal = arguments.get(0);
        final String tokenName = arguments.get(1);
        if (false == ServiceAccountService.isServiceAccountPrincipal(principal)) {
            throw new UserException(
                ExitCodes.NO_USER,
                "Unknown service account principal: ["
                    + principal
                    + "]. Must be one of ["
                    + Strings.collectionToDelimitedString(ServiceAccountService.getServiceAccountPrincipals(), ",")
                    + "]"
            );
        }
        if (false == Validation.isValidServiceAccountTokenName(tokenName)) {
            throw new UserException(ExitCodes.CODE_ERROR, Validation.formatInvalidServiceTokenNameErrorMessage(tokenName));
        }
        return new ServiceAccountTokenId(ServiceAccountId.fromPrincipal(principal), tokenName);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy