Needed explanation for keystore's PrivateKey entries

I encountered the following issue when I try to enroll a token for Kibana:

$ ./bin/elasticsearch-create-enrollment-token --scope kibana
Unable to create enrollment token for scope [kibana]

ERROR: Unable to create an enrollment token. Elasticsearch node HTTP layer SSL configuration Keystore doesn't contain any PrivateKey entries where the associated certificate is a CA certificate, with exit code 73

So I found this thread.

When I follow the author's instruction, after the command

./jdk/bin/keytool -importkeystore -destkeystore /etc/elasticsearch/certs/new-keystore.p12 -srckeystore /etc/elasticsearch/certs/http.p12  -srcstoretype PKCS12

, it yields the error:

keytool error: java.security.UnrecoverableKeyException: Get Key failed: Given final block not properly padded. Such issues can arise if a bad key is used during decryption

Another post suggests me to set
xpack.security.transport.ssl.keystore.secure_password and xpack.security.transport.ssl.truststore.secure_password.

But at this point, I want to understand what is really going on.

What does the error "Elasticsearch node HTTP layer SSL configuration Keystore doesn't contain any PrivateKey entries where the associated certificate is a CA certificate" really mean ?

I don't understand since when I do keytool -keystore /etc/elasticsearch/certs/http.ca -list, it shows that I have one entry PrivateKeyEntry:

Your keystore contains 1 entry

http, Jul 5, 2025, PrivateKeyEntry, 
Certificate chain length: 0

Continuing the discussion from Import CA Cert as PrivateKeyEntry to HTTP Keystore - Solve Unable to create enrollment token Error, the author wrote that:

... but I still does not understand that sentence.

Thank you!

Apologies if my previous post was unclear — English is not my first language, and I realize I could have explained it better.

What I meant to is that the error message indicates that the HTTPS configuration in Elasticsearch expects a PrivateKeyEntry, not a trustedCertEntry. This means the keystore used must contain a private key (typically from a CA-signed certificate), not just trusted certificates.

In the context of Elasticsearch 7 and 8, you can inspect the keystore using the command:

keytool -keystore <your-http-pkcs-path.p12> -list

Here’s an example of what you might see when the keystore contains a trustedCertEntry:

Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 2 entries
   ca, Sep 6, 2022, trustedCertEntry,
   Certificate fingerprint (SHA-256): 25:27:7D:EE:FE:F6:54:57:47:BE:B5:10:C4:90:DF:28:BF:1B:3B:F9:5E:47:F5:34:5F:03:38:1E:84:0A:23:E7
   http, Sep 6, 2022, PrivateKeyEntry,
   Certificate fingerprint (SHA-256): D4:EF:60:2C:E5:2D:4C:A8:33:C0:49:44:F4:B5:38:19:92:97:72:CB:5D:85:20:A4:97:9B:90:24:D0:0C:D1:FB

But what we actually need is something like this — both entries must be of type PrivateKeyEntry:

Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 2 entries
   ca, Sep 5, 2022, PrivateKeyEntry,
   Certificate fingerprint (SHA-256): 53:F4:9A:9D:56:A9:3A:AF:90:94:41:FA:D7:15:3F:DF:C1:39:AC:BA:FF:12:44:C0:36:4D:15:4C:20:14:1E:3D
   http, Sep 5, 2022, PrivateKeyEntry,
   Certificate fingerprint (SHA-256): EF:8D:78:EC:F0:C5:97:1B:7B:58:EF:5F:E3:73:A5:D0:7E:1B:FE:B3:75:B0:B4:D9:CB:80:FC:B3:8E:5D:A5:74

The key point is that your HTTP certificate must be imported in a way that includes both the private key and its full chain of trust, resulting in entries marked as PrivateKeyEntry.

According to the documentation for Elasticsearch 7.x and 8.x, we usually receive a .p12 file that contains the CA certificate — by default, it's named elastic-stack-ca.p12. We also get a ZIP file intended for HTTPS usage, which contains http.p12 after extraction.

To replace the existing trustedCertEntry with a valid PrivateKeyEntry, you can run the following command:

keytool -importkeystore -destkeystore http.p12 -srckeystore elastic-stack-ca.p12 -srcstoretype PKCS12

If you're working with Elasticsearch 9.0+, I recommend following the latest official documentation, as some of the commands have changed. I haven’t verified whether the same process works in newer versions — this explanation is mainly meant to clarify my previous post and provide additional context.

I hope this time my explanation is clear enough.

1 Like

The short answer is that you're trying to use enrollment tokens on a cluster that doesn't use security auto-configuration.

That can be made to work, but it requires following a number of undocumented steps.

The general premise is:

  • If you let the cluster auto configure security, then use the enrollment utilities
  • If you configured security yourself, then configure everything yourself, including Kibana.

It would be nice to support more middle ground, but that's not the case right now.

Rather than messing about with keystores, what you really want is to just configure Kibana yourself.

I would love to be able to point you to a simple set of instructions for that, but I think it has been lost in multiple rounds of docs updates.

The short answer:

Step 1:
Create a service token for Kibana

POST  /_security/service/elastic/kibana/credential/token/kibana1

Step 2:
Copy the value from that API into your Kibana YAML settings under elasticsearch.serviceAccountToken

kibana.yml

elasticsearch.serviceAccountToken: "AAEAAWVsYXN0aWM...vZmxlZXQtc2VydmVyL3Rva2VuMTo3TFdaSDZ" 

Step 3:
Configure TLS trust between Kibana and Elasticsearch if necessary.
I can't give you direct instructions for this step without knowing what your Elasticsearch SSL configuration looks like.
You probably want to configure elasticsearch.ssl.certificateAuthorities in your kibana.yml to point to the PEM encoded CA for Elasticsearch (something like ca.crt or ca.pem)

That's all the enrollment token is really doing, and you could try and make it work, but it's probably easier to do it by hand.

1 Like

Kibana opens successfully now but I did some things that you didn't mention so I want to make sure I did the right thing and followed best practice.

  1. Create a service token for Kibana via the API
  2. Copy the value from that API into your Kibana YAML settings under elasticsearch.serviceAccountToken
  3. Configure TLS between Kibana and ES. For that I set elasticsearch.ssl.certificateAuthorities to http_ca.crt (The certificate of the ES instance)
  4. After these steps were done, Kibana still asked for the token on the webUI despite me doing Step 2. I put the same token from step 2 on the form.
  5. Kibana also asked me if I trust the certificate despite me doing Step 3.

Why was step 4 and 5 necessary ? Or is it because I misconfigured step 2 and step 3 so these two steps were not considered by Kibana ?

Hi @HarimbolaSantatra
Please share your kibana.yml

Here is my kibana.yml:

elasticsearch.ssl.certificateAuthorities: [/var/lib/kibana/ca_1754335525818.crt]
logging.appenders.file.type: file
logging.appenders.file.fileName: /var/log/kibana/kibana.log
logging.appenders.file.layout.type: json
logging.root.appenders: [default, file]
pid.file: /run/kibana/kibana.pid
elasticsearch.hosts: [https://192.168.3.1:9200]
elasticsearch.username: kibana_system
elasticsearch.password: <THE_PASSWORD_FROM_LAST_STEP>
xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [https://192.168.3.1:9200], ca_trusted_fingerprint: 722eea01ba32bd8e2f4fa0a802b2023c1ce55ec1411ba401ca610635261cec27}]

Thank you for your support!

I do not see where you added the service account in your yaml file

elasticsearch.serviceAccountToken

Well so you still have the username and password which you wouldn't need with the service account.

Sorry I didn't pay attention.

The elasticsearch.serviceAccountToken and the elasticsearch.ssl.certificateAuthorities that I configured was automatically commented.

In kibana.yml there is also these lines before the snippets I inserted:

### >>>>>>> BACKUP END: Kibana interactive setup (2025-08-04T19:25:25.822Z)

# This section was automatically generated during setup.

So I guess the configuration that I did was discarded automatically after the configuration on the Web UI.