VaultService.java

/*
 * Copyright (c) 2002-2023, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.plugins.vault.service;

import com.bettercloud.vault.Vault;
import com.bettercloud.vault.VaultException;
import com.bettercloud.vault.api.Auth;
import com.bettercloud.vault.response.AuthResponse;
import fr.paris.lutece.plugins.vault.business.*;
import fr.paris.lutece.plugins.vault.business.Properties;
import fr.paris.lutece.plugins.vault.rs.VaultAPI;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.util.ReferenceItem;
import fr.paris.lutece.util.ReferenceList;

import java.util.*;

/**
 * The type Vault service.
 */
public class VaultService
{

    private ReferenceList _listEnvAccessor = new ReferenceList( );
    private List<Properties> _listEnvSecrets;

    private static VaultService _instance = null;
    private Vault _vault;

    /**
     * Instantiates a new Vault service.
     *
     * @param strAdress
     *            the str adress
     * @param strVaultToken
     *            the str vault token
     */
    public VaultService( String strAdress, String strVaultToken )
    {
        init( strAdress, strVaultToken );

    }

    /**
     * Gets instance.
     *
     * @return the instance
     */
    public static VaultService getInstance( )
    {
        if ( _instance == null )
        {

            _instance = new VaultService( AppPropertiesService.getProperty( "vault.vaultServerAdress" ),
                    AppPropertiesService.getProperty( "vault.rootToken" ) );
        }
        return _instance;
    }

    private void init( String strAdress, String strVaultToken )
    {

        this._vault = VaultUtil.initDriver( strAdress, strVaultToken );

    }

    /**
     * Create environnement token string.
     *
     * @param appCode
     *            the app code
     * @param env
     *            the env
     * @return the string
     * @throws VaultException
     *             the vault exception
     */
    public String createEnvironnementToken( String appCode, Environnement env ) throws VaultException
    {

        // Creation of the policy; combination of appCode and envCode to form the name
        VaultAPI.createPolicy( appCode, env );

        // Auth.TokenRequest is asking for a list of policies so we create it
        List<String> policies = new ArrayList<>( );
        policies.add( appCode.toLowerCase( ) + env.getCode( ).toLowerCase( ) );

        // Create the tokenRequest and adding the list of policies to it
        Auth.TokenRequest tokenRequest = new Auth.TokenRequest( );
        tokenRequest.polices( policies );
        tokenRequest.displayName( appCode + env.getCode( ) );

        // Token creation with the tokenRequest composed of the policy
        AuthResponse vaultToken = _vault.auth( ).createToken( tokenRequest );
        // Adding token accessor to the Environnement Object : can revoke token using accessor
        _listEnvAccessor.addItem( env.getId( ), vaultToken.getTokenAccessor( ) );
        return vaultToken.getAuthClientToken( );

    }

    /**
     * Regenerate token string.
     *
     * @param appCode
     *            the app code
     * @param environnement
     *            the environnement
     * @return the string
     * @throws VaultException
     *             the vault exception
     */
    public String regenerateToken( String appCode, Environnement environnement ) throws VaultException
    {

        VaultAPI.removeTokenJackson( environnement.getToken( ) );
        _listEnvAccessor.remove( VaultService.getInstance( ).getEnvAccessorObject( environnement.getId( ) ) );

        List<String> policies = new ArrayList<>( );
        policies.add( appCode.toLowerCase( ) + environnement.getCode( ).toLowerCase( ) );

        // Create the tokenRequest and adding the list of policies to it
        Auth.TokenRequest tokenRequest = new Auth.TokenRequest( );
        tokenRequest.polices( policies );
        tokenRequest.displayName( appCode + environnement.getCode( ) );

        // Token creation with the tokenRequest composed of the policy
        AuthResponse vaultToken = _vault.auth( ).createToken( tokenRequest );

        // Adding token accessor to the Environnement Object : can revoke token using accessor
        _listEnvAccessor.addItem( environnement.getId( ), vaultToken.getTokenAccessor( ) );
        return vaultToken.getAuthClientToken( );

    }

    public void renameEnvironnement(Application application, Environnement environnement, String strOldCode, String strOldToken) throws VaultException {

        String currentCode = environnement.getCode();
        String currentToken = environnement.getToken();

        List<String> secretList = _vault.logical( ).list( AppPropertiesService.getProperty("vault.secretPath")+"/"+application.getCode()+"/"+strOldCode ).getListData( );
        if ( !secretList.isEmpty( ) )
        {
            secretList.forEach( x -> {
                try
                {
                    final String secretV = _vault.logical( ).read( AppPropertiesService.getProperty("vault.secretPath")+"/"+application.getCode()+"/"+strOldCode + "/" + x ).getData( ).get( x );
                    VaultService.getInstance().writeSecret(x,secretV,application,environnement);
                }
                catch( VaultException e )
                {
                    AppLogService.error( "Erreur pour supprimer l'environnement", e );
                }
            } );
        }
        environnement.setCode(strOldCode);
        environnement.setPath(EnvironnementUtil.getEnvironmentPath(application.getCode(),strOldCode));
        environnement.setToken(strOldToken);
        VaultService.getInstance().removeEnv(environnement.getToken(),application.getCode(),environnement);
        environnement.setCode(currentCode);
        environnement.setToken(currentToken);
    }

    /**
     * Gets env accessor.
     *
     * @param idEnv
     *            the id env
     * @return the env accessor
     */
    public String getEnvAccessor( Integer idEnv )
    {
        if ( !_listEnvAccessor.isEmpty( ) )
        {
            for ( ReferenceItem env : _listEnvAccessor )
            {
                if ( Objects.equals( env.getCode( ), idEnv.toString( ) ) )
                {
                    return env.getName( );
                }
            }
        }
        return null;
    }

    /**
     * Gets env accessor object.
     *
     * @param idEnv
     *            the id env
     * @return the env accessor object
     */
    public ReferenceItem getEnvAccessorObject( Integer idEnv )
    {
        if ( !_listEnvAccessor.isEmpty( ) )
        {
            for ( ReferenceItem env : _listEnvAccessor )
            {
                if ( Objects.equals( env.getCode( ), idEnv.toString( ) ) )
                {
                    return env;
                }
            }
        }
        return null;
    }

    /**
     * Remove env.
     *
     * @param token
     *            the token
     * @param appCode
     *            the app code
     * @param environnement
     *            the environnement
     * @throws VaultException
     *             the vault exception
     */
    public void removeEnv( String token, String appCode, Environnement environnement ) throws VaultException
    {

        List<String> secretList = _vault.logical( ).list( environnement.getPath( ) ).getListData( );
        if ( !secretList.isEmpty( ) )
        {
            secretList.forEach( x -> {
                try
                {
                    _vault.logical( ).delete( environnement.getPath( ) + "/" + x );
                }
                catch( VaultException e )
                {
                    AppLogService.error( "Erreur pour supprimer l'environnement", e );
                }
            } );
        }
        VaultAPI.removePolicy( appCode.toLowerCase( ) + environnement.getCode( ).toLowerCase( ) );
//        VaultAPI.removeToken( token );
        VaultAPI.removeTokenJackson(token);
        _listEnvAccessor.remove( VaultService.getInstance( ).getEnvAccessorObject( environnement.getId( ) ) );

    }


    /**
     * Write secret.
     *
     * @param secret
     *            the secret
     * @param value
     *            the value
     * @param application
     *            the application
     * @param environnement
     *            the environnement
     * @throws VaultException
     *             the vault exception
     */
    public void writeSecret( String secret, String value, Application application, Environnement environnement ) throws VaultException
    {

        if ( secret == null || value == null || application.getCode( ) == null || environnement.getCode( ) == null )
        {
            throw new VaultException( "Paramètres incorrects" );
        }

        final Map<String, Object> secrets = new HashMap<String, Object>( );
        secrets.put( secret, value );

        // Write operation
        _vault.logical( ).write( environnement.getPath( ) + "/" + secret, secrets );

    }

    /**
     * Delete secret.
     *
     * @param secret
     *            the secret
     * @param application
     *            the application
     * @param environnement
     *            the environnement
     * @throws VaultException
     *             the vault exception
     */
    public void deleteSecret( String secret, Application application, Environnement environnement ) throws VaultException
    {

        if ( secret == null || application.getCode( ) == null || environnement.getCode( ) == null )
        {
            throw new VaultException( "Paramètres incorrects" );
        }

        _vault.logical( ).delete( environnement.getPath( ) + "/" + secret );

    }

    /**
     * Update secret.
     *
     * @param secret
     *            the secret
     * @param value
     *            the value
     * @param application
     *            the application
     * @param environnement
     *            the environnement
     * @throws VaultException
     *             the vault exception
     */
    public void updateSecret( String secret, String value, Application application, Environnement environnement ) throws VaultException
    {
        if ( secret == null || value == null || application.getCode( ) == null || environnement.getCode( ) == null )
        {
            throw new VaultException( "Paramètres incorrects" );
        }

        VaultService.getInstance( ).deleteSecret( secret, application, environnement );
        VaultService.getInstance( ).writeSecret( secret, value, application, environnement );
    }

    /**
     * Gets all secrets.
     *
     * @return the all secrets
     */
    public List<String> getAllSecrets( )
    {

        try
        {

            List<String> listAllSecrets = _vault.logical( ).list( AppPropertiesService.getProperty( "vault.secretPath" ) + "/" ).getListData( );
            return listAllSecrets;

        }
        catch( VaultException e )
        {

            AppLogService.error( "Erreur pour récupérer la valeur du secret", e );

        }

        return null;
    }

    /**
     * Gets secrets by env.
     *
     * @param application
     *            the application
     * @param environnement
     *            the environnement
     * @return the secrets by env
     */
    public List<Properties> getSecretsByEnv( Application application, Environnement environnement )
    {

        _listEnvSecrets = new ArrayList<>( );

        try
        {
            final List<String> secretList = _vault.logical( ).list( environnement.getPath( ) ).getListData( );
            for ( int i = 0; i < secretList.size( ); i++ )
            {
                int appId = application.getId( );
                int envId = environnement.getId( );
                Properties _properties = new Properties( );
                _properties.setKey( secretList.get( i ) );
                _properties.setIdenvironnement( envId );
                _properties.setValue( VaultService.getInstance( ).getDetailsSecret( secretList.get( i ), environnement ) );
                _listEnvSecrets.add( _properties );
            }
            return _listEnvSecrets;

        }
        catch( VaultException e )
        {

            AppLogService.error( "Erreur pour récupérer la liste des secretst", e );

        }

        return null;
    }

    /**
     * Gets details secret.
     *
     * @param secretKey
     *            the secret key
     * @param environnement
     *            the environnement
     * @return the details secret
     */
    public String getDetailsSecret( String secretKey, Environnement environnement )
    {

        try
        {

            final String secretKV = _vault.logical( ).read( environnement.getPath( ) + "/" + secretKey ).getData( ).get( secretKey );
            return secretKV;

        }
        catch( VaultException e )
        {

           AppLogService.error( "Erreur pour récupérer la valeur du secret",e);
            e.printStackTrace( );

        }

        return secretKey;
    }

    /**
     * Gets secret.
     *
     * @param secretKey
     *            the secret key
     * @param application
     *            the application
     * @param environnement
     *            the environnement
     * @return the secret
     */
    public Properties getSecret( String secretKey, Application application, Environnement environnement )
    {

        try
        {

            final String secretValue = _vault.logical( ).read( environnement.getPath( ) + "/" + secretKey ).getData( ).get( secretKey );

            Properties secret = new Properties( );
            secret.setIdenvironnement( environnement.getId( ) );
            secret.setKey( secretKey );
            secret.setValue( secretValue );
            return secret;

        }
        catch( VaultException e )
        {

            AppLogService.error( "Erreur pour récupérer la valeur du secret" );
            e.printStackTrace( );

        }

        return null;
    }

}