3

I installed Vault locally. I was able to start local dev server and write/read some secrets into Vault kv based on this official tutorial https://learn.hashicorp.com/vault/

Then I wanted to create some very basic Java/Spring Boot demo client that would connect to my local Vault dev server in order to write/read secrets. I read Baeldung tutorial for inspiration https://www.baeldung.com/spring-vault.

This is my vault-config.properties:

vault.uri=http://127.0.0.1:8200

vault.token=s.EXg6MQwUuB63Z7Xra4zybOut (token generated after the latest start of server)

Then service class:

@Service
public class CredentialsService {

    @Autowired
    private VaultTemplate vaultTemplate;

    public void secureCredentials(Credentials credentials) throws URISyntaxException {
        vaultTemplate.write("credentials/myapp", credentials);
    }

    public Credentials accessCredentials() throws URISyntaxException {
        VaultResponseSupport<Credentials> response = vaultTemplate.read("credentials/myapp", Credentials.class);
        return response.getData();
    }
}

Configuration class:

@Configuration
public class VaultConfig extends AbstractVaultConfiguration {

    @Override
    public ClientAuthentication clientAuthentication() {
        return new TokenAuthentication("s.EXg6MQwUuB63Z7Xra4zybOut");
    }

    @Override
    public VaultEndpoint vaultEndpoint() {
        return VaultEndpoint.create("host", 8200);
    }
}

and this:

@Configuration
@PropertySource(value = { "vault-config.properties" })
@Import(value = EnvironmentVaultConfiguration.class)
public class VaultEnvironmentConfig {

}

One domain object:

public class Credentials {

    private String username;
    private String password;

    public Credentials() {

    }
    public Credentials(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    @Override
    public String toString() {
        return "Credential [username=" + username + ", password=" + password + "]";
    }
}

And finally my main Spring Boot class:

@RestController
@ComponentScan
@SpringBootApplication
public class SpringVaultTutorial {

    @Autowired
    CredentialsService credentialsService;

    @RequestMapping("/")
    String home() throws URISyntaxException {
        Credentials credentials = new Credentials("oliver","exxeta123");
        credentialsService.secureCredentials(credentials);
        return credentialsService.accessCredentials().getUsername().toString();
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringVaultTutorial.class, args);
    }

}

Main class should write secret and then immediately read it and print username. But I am getting this error message:

There was an unexpected error (type=Internal Server Error, status=500). I/O error on POST request for "https://host:8200/v1/credentials/myapp": host; nested exception is java.net.UnknownHostException: host

Does somebody have a clue what can be wrong?

EDIT: Based on advice from Arun I followed this tutorial https://drissamri.be/blog/java/enable-https-in-spring-boot/

I have been trying both approaches. 1) Modify application.properties:

server.port: 8443
server.ssl.key-store: keystore.p12
server.ssl.key-store-password: oliver
server.ssl.keyStoreType: PKCS12
server.ssl.keyAlias: tomcat
security.require-ssl=true

After modification, when I call https://localhost:8443, I am getting Exception: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection? at sun.security.ssl.InputRecord.handleUnknownRecord(InputRecord.java:710) ~[na:1.8.0_121] at sun.security.ssl.InputRecord.read(InputRecord.java:527) ~[na:1.8.0_121] at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:973) ~[na:1.8.0_121] at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375) ~[na:1.8.0_121] at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403) ~[na:1.8.0_121] at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387) ~[na:1.8.0_121]

2) Second approach based on tutorial is about adding ConnectorConfig class:

@Configuration
public class ConnectorConfig {

    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat =
                new TomcatServletWebServerFactory() {
                    @Override
                    protected void postProcessContext(Context context) {
                        SecurityConstraint securityConstraint = new SecurityConstraint();
                        securityConstraint.setUserConstraint("CONFIDENTIAL");
                        SecurityCollection collection = new SecurityCollection();
                        collection.addPattern("/*");
                        securityConstraint.addCollection(collection);
                        context.addConstraint(securityConstraint);
                    }
                };
        tomcat.addAdditionalTomcatConnectors(redirectConnector());
        return tomcat;
    }

    private Connector redirectConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setPort(8090);
        connector.setSecure(false);
        connector.setRedirectPort(8443);
        return connector;
    }
}

But after calling localhost:8090 that redirects me to https://localhost:8443, I am getting the same error: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection? at sun.security.ssl.InputRecord.handleUnknownRecord(InputRecord.java:710) ~

Now the question is: Do I have to configure something also on the Vault side regarding certificate? Or do you think there could be some certificate problem on Java client side? But I thing if there was Java certificate problem, exception would be thrown already during startup.

Oliver Eder
  • 85
  • 2
  • 10

2 Answers2

2

Problem solved. Now I am able to connect to local Vault from Java client. I paste code here if someone in the future wants to run simple Java Client - Vault demo.

Controller:

@RestController
@RequestMapping(Paths.ROOT)
@Api(value = Paths.ROOT, description = "Endpoint for core testing")
public class Controller {

    @Autowired
    CredentialsService credentialsService;

    @GetMapping("/")
    String home() throws URISyntaxException {
        Credentials credentials = new Credentials("oliver", "exxeta123");
        credentialsService.secureCredentials(credentials);
        return credentialsService.accessCredentials().toString();
    }

    @GetMapping("/test")
    public String test() throws IOException {
        // http://127.0.0.1:8200/v1/sys/internal/ui/mounts/secret/mysecrets
        VaultConfig vc = new VaultConfig();
        String bearerToken = vc.clientAuthentication().login().getToken();
        System.out.println(bearerToken);
        // credentialsService.accessCredentials()

        // Sending get request
        //URL url = new URL("http://127.0.0.1:8200/v1/sys/internal/ui/mounts/secret/mysecrets");
        // URL updated to match readme.adoc
        URL url = new URL("http://127.0.0.1:8200/v1/kv/my-secret");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        conn.setRequestProperty("Authorization", "Bearer " + bearerToken);
        conn.setRequestProperty("Content-Type", "application/json");
        conn.setRequestMethod("GET");

        BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String output;

        StringBuffer response = new StringBuffer();
        while ((output = in.readLine()) != null) {
            response.append(output);
        }

        in.close();
        // printing result from response
        return "Response: - " + response.toString();
    }

    @GetMapping(value = { "/add/{name}/{username}/{password}" })
    public ResponseEntity<String> addKey(@PathVariable(value = "name", required = false, name = "name") String name,
            @PathVariable(value = "username", required = false, name = "username") String username,
            @PathVariable(value = "password", required = false, name = "password") String password) throws URISyntaxException {
        Credentials credentials = new Credentials(username, password);
        credentialsService.secureCredentials(name, credentials);
        return new ResponseEntity<>("Add success: " + credentialsService.accessCredentials(name).getUsername(), HttpStatus.OK);
    }

    @GetMapping(value = {"/get", "/get/{name}"})
    public ResponseEntity<Credentials> getKey(@PathVariable(value = "name", required = false, name = "name") String name) {
        return new ResponseEntity<>(credentialsService.accessCredentials(name), HttpStatus.OK);
    }

    @GetMapping(value= {"/delete", "/delete/{name}"})
    public String removeKey(@PathVariable(value = "name", required = false, name = "name") String name) {
        return "Delete success: " + credentialsService.deleteCredentials(name);
    }

}

Service:

@Service
public class CredentialsService {

    private VaultTemplate vaultTemplate;

    /**
     * To Secure Credentials
     *
     * @param credentials
     * @return VaultResponse
     * @throws URISyntaxException
     */
    public void secureCredentials(Credentials credentials) throws URISyntaxException {
        //vaultTemplate.write("credentials/myapp", credentials);
        initVaultTemplate();
        vaultTemplate.write("kv/myapp", credentials);
    }


    public void secureCredentials(String storagePlace, Credentials credentials) {
        initVaultTemplate();
        vaultTemplate.write("kv/" + storagePlace, credentials);
    }


    /**
     * To Retrieve Credentials
     *
     * @return Credentials
     * @throws URISyntaxException
     */
    public Credentials accessCredentials() throws URISyntaxException {
        //VaultResponseSupport<Credentials> response = vaultTemplate.read("credentials/myapp", Credentials.class);
        initVaultTemplate();
        VaultResponseSupport<Credentials> response = vaultTemplate.read("kv/myapp", Credentials.class);
        return response.getData();
        // TODO special case when there are no values
    }

    /**
     * @param nameOfsecrets key name
     * @return if is presented or empty object
     */
    public Credentials accessCredentials(String nameOfsecrets) {
        initVaultTemplate();
        VaultResponseSupport<Credentials> response = vaultTemplate.read("kv/" + nameOfsecrets, Credentials.class);
        if (response != null) {
            return response.getData();
        } else {
            return new Credentials();
        }
    }

    public boolean deleteCredentials(String name) {
        initVaultTemplate();
        vaultTemplate.delete("kv/" + name);
        return true;
    }
}

private void initVaultTemplate() {
            VaultEndpoint endpoint = new VaultEndpoint();
            endpoint.setHost("localhost");
            endpoint.setPort(8200);
            endpoint.setScheme("http");

            vaultTemplate = new VaultTemplate(endpoint, new VaultConfig().clientAuthentication());
        }

VaultConfig:

@Configuration
public class VaultConfig extends AbstractVaultConfiguration {

    @Override
    public ClientAuthentication clientAuthentication() {
        return new TokenAuthentication("00000000-0000-0000-0000-000000000000");
    }

    @Override
    public VaultEndpoint vaultEndpoint() {
        return VaultEndpoint.create("localhost", 8200);
    }

}

VaultEnvironmentConfig:

@Configuration
@PropertySource(value = { "vault-config.properties" })
@Import(value = EnvironmentVaultConfiguration.class)
public class VaultEnvironmentConfig {

}

Main class:

@SpringBootApplication
@EnableSwagger2
public class SpringVaultTutorial {

    public static void main(String[] args) {
        SpringApplication.run(SpringVaultTutorial.class, args);
    }

    //SWAGGER DOCUMENTATION BEANS
    // default group contains all endpoints
    @Bean
    public Docket defaultApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())//all
                .build().apiInfo(apiInfo());
    }

    // Management group contains Spring Actuator endpoints
    @Bean
    public Docket swaggerAdminEndpoints() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName(Paths.ROOT)
                .apiInfo(apiInfo())
                .select()
                .paths(PathSelectors.regex("/v1/.*"))
                .build()
                .forCodeGeneration(true);
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Vault Demo Application")
                .description("Demo Application using vault")
                .version("1.0")
                .build();
    }

}

vault-config.properties:

vault.uri=http://127.0.0.1:8200 vault.token=00000000-0000-0000-0000-000000000000

application.properties:

    server.port=8443
    server.ssl.key-alias=selfsigned_localhost_sslserver
    server.ssl.key-password=changeit
    server.ssl.key-store=classpath:ssl-server.jks
    server.ssl.key-store-provider=SUN
    server.ssl.key-store-type=JKS

Paths:

public class Paths {
    public static final String ROOT = "/v1";
}

Credentials:

public class Credentials {

    private String username;
    private String password;

    public Credentials() {

    }

    public Credentials(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    @Override
    public String toString() {
        return "Credential [username=" + username + ", password=" + password + "]";
    }

}
Oliver Eder
  • 85
  • 2
  • 10
  • it is worth mentioning VaultEndpoint.from(URI(vaultUrl)) as an alternative to dealing with VaultTemplate – itavq Sep 24 '19 at 08:52
1

UnknownHostException is due to no server is available with the name 'host'. you can either add an entry in hosts file map to localhost. or try changing the host name while creating vault as

    @Override
    public VaultEndpoint vaultEndpoint() {
        return VaultEndpoint.create("localhost", 8200);
    }
Arun
  • 336
  • 5
  • 14
  • Thank you Arunachalam ! Your advice helped me. But now I am getting new error: Unrecognized SSL message, plaintext connection?; nested exception is javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection? Do you know how to fix it? – Oliver Eder Mar 12 '19 at 21:39
  • when ssl is enabled, you need to give a certificate https://drissamri.be/blog/java/enable-https-in-spring-boot/ you may try disabling it for your local testing! – Arun Mar 12 '19 at 21:44
  • Hi Arun. I followed that SSL tutorial. So I added this into my application.properties: server.port: 8443 server.ssl.key-store: keystore.p12 server.ssl.key-store-password: oliver server.ssl.keyStoreType: PKCS12 server.ssl.keyAlias: tomcat security.require-ssl=true I created my own certificate with keygen. But after I call https://localhost:8443/ I am still getting the same error Unrecognized SSL message, plaintext connection? Did you experience similar problem when trying to access local Vault server via SSL? Thank you in advance for any help – Oliver Eder Mar 13 '19 at 12:04
  • after enabling the ssl with spring-boot, did you call https://localchost:8443? does it throw any certifications to accept? Can you share a stack trace if any? – Arun Mar 13 '19 at 17:34
  • Hi Arun. I tried both approaches from tutorial. I added description to the original question under Edit section. Please check if you have time. Thank you ! – Oliver Eder Mar 14 '19 at 09:47