or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication-sessions.mdcomponent-framework.mdcore-models.mdcredential-management.mdindex.mdorganization-management.mdprovider-framework.mdsession-management.mduser-storage.mdvalidation-framework.mdvault-integration.md

vault-integration.mddocs/

0

# Vault Integration

1

2

The vault integration framework provides secure secrets management by integrating with external vault systems. It enables Keycloak to retrieve sensitive configuration values like passwords, API keys, and certificates from secure storage.

3

4

## Core Vault Interfaces

5

6

### VaultProvider

7

8

Provider interface for vault implementations.

9

10

```java { .api }

11

public interface VaultProvider extends Provider {

12

/**

13

* Obtains a raw secret from the vault.

14

*

15

* @param vaultSecretId the vault secret identifier

16

* @return vault raw secret or empty optional

17

*/

18

VaultRawSecret obtainSecret(String vaultSecretId);

19

}

20

```

21

22

### VaultTranscriber

23

24

Transcribes vault references in configuration values to actual secret values.

25

26

```java { .api }

27

public interface VaultTranscriber {

28

/**

29

* Transcribes a value, replacing vault references with actual secret values.

30

*

31

* @param value the value potentially containing vault references

32

* @return transcribed value with secrets resolved

33

*/

34

String transcribe(String value);

35

}

36

```

37

38

## Vault Secret Types

39

40

### VaultRawSecret

41

42

Base interface for raw vault secrets.

43

44

```java { .api }

45

public interface VaultRawSecret extends AutoCloseable {

46

/**

47

* Gets the raw secret as byte array.

48

*

49

* @return optional raw secret bytes

50

*/

51

Optional<byte[]> get();

52

53

/**

54

* Gets the raw secret as byte array or default value.

55

*

56

* @param defaultValue default value if secret not found

57

* @return raw secret bytes or default

58

*/

59

default byte[] getOrDefault(byte[] defaultValue) {

60

return get().orElse(defaultValue);

61

}

62

63

@Override

64

void close();

65

}

66

```

67

68

### VaultCharSecret

69

70

Interface for character-based vault secrets.

71

72

```java { .api }

73

public interface VaultCharSecret extends VaultRawSecret {

74

/**

75

* Gets the secret as character array.

76

*

77

* @return optional character array

78

*/

79

Optional<char[]> getAsArray();

80

81

/**

82

* Gets the secret as character array or default value.

83

*

84

* @param defaultValue default value if secret not found

85

* @return character array or default

86

*/

87

default char[] getAsArray(char[] defaultValue) {

88

return getAsArray().orElse(defaultValue);

89

}

90

91

@Override

92

default Optional<byte[]> get() {

93

return getAsArray().map(chars -> {

94

byte[] bytes = new byte[chars.length];

95

for (int i = 0; i < chars.length; i++) {

96

bytes[i] = (byte) chars[i];

97

}

98

return bytes;

99

});

100

}

101

}

102

```

103

104

### VaultStringSecret

105

106

Interface for string-based vault secrets.

107

108

```java { .api }

109

public interface VaultStringSecret extends VaultCharSecret {

110

/**

111

* Gets the secret as string.

112

*

113

* @return optional string value

114

*/

115

Optional<String> get();

116

117

/**

118

* Gets the secret as string or default value.

119

*

120

* @param defaultValue default value if secret not found

121

* @return string value or default

122

*/

123

default String getOrDefault(String defaultValue) {

124

return get().orElse(defaultValue);

125

}

126

127

@Override

128

default Optional<char[]> getAsArray() {

129

return get().map(String::toCharArray);

130

}

131

}

132

```

133

134

## Vault Key Resolution

135

136

### VaultKeyResolver

137

138

Resolves vault keys from configuration values.

139

140

```java { .api }

141

public interface VaultKeyResolver {

142

/**

143

* Checks if a value contains a vault key reference.

144

*

145

* @param value the value to check

146

* @return true if value contains vault reference

147

*/

148

boolean isVaultKey(String value);

149

150

/**

151

* Extracts the vault key from a vault reference.

152

*

153

* @param value the vault reference value

154

* @return vault key or null if not a vault reference

155

*/

156

String getVaultKey(String value);

157

158

/**

159

* Creates a vault reference from a key.

160

*

161

* @param key the vault key

162

* @return vault reference string

163

*/

164

String createVaultReference(String key);

165

}

166

```

167

168

## Vault SPI

169

170

### VaultSpi

171

172

SPI definition for vault providers.

173

174

```java { .api }

175

public class VaultSpi implements Spi {

176

@Override

177

public boolean isInternal() {

178

return true;

179

}

180

181

@Override

182

public String getName() {

183

return "vault";

184

}

185

186

@Override

187

public Class<? extends Provider> getProviderClass() {

188

return VaultProvider.class;

189

}

190

191

@Override

192

public Class<? extends ProviderFactory> getProviderFactoryClass() {

193

return VaultProviderFactory.class;

194

}

195

}

196

```

197

198

### VaultProviderFactory

199

200

Factory for creating vault provider instances.

201

202

```java { .api }

203

public interface VaultProviderFactory extends ProviderFactory<VaultProvider> {

204

/**

205

* Creates a vault provider instance.

206

*

207

* @param session Keycloak session

208

* @return vault provider

209

*/

210

@Override

211

VaultProvider create(KeycloakSession session);

212

213

/**

214

* Gets the provider ID.

215

*

216

* @return provider ID

217

*/

218

@Override

219

String getId();

220

}

221

```

222

223

## Usage Examples

224

225

### Creating a Custom Vault Provider

226

227

```java

228

public class HashiCorpVaultProvider implements VaultProvider {

229

private final VaultClient vaultClient;

230

private final String mountPath;

231

232

public HashiCorpVaultProvider(VaultClient vaultClient, String mountPath) {

233

this.vaultClient = vaultClient;

234

this.mountPath = mountPath;

235

}

236

237

@Override

238

public VaultRawSecret obtainSecret(String vaultSecretId) {

239

try {

240

// Parse vault secret ID (e.g., "secret/myapp/database")

241

String[] parts = vaultSecretId.split("/");

242

if (parts.length < 2) {

243

return new EmptyVaultSecret();

244

}

245

246

String path = vaultSecretId;

247

if (!path.startsWith(mountPath)) {

248

path = mountPath + "/" + path;

249

}

250

251

// Retrieve secret from HashiCorp Vault

252

Map<String, Object> secretData = vaultClient.read(path);

253

if (secretData == null || secretData.isEmpty()) {

254

return new EmptyVaultSecret();

255

}

256

257

// Return the secret value

258

Object secretValue = secretData.get("value");

259

if (secretValue == null) {

260

// Try default field names

261

secretValue = secretData.get("password");

262

if (secretValue == null) {

263

secretValue = secretData.get("secret");

264

}

265

}

266

267

if (secretValue != null) {

268

return new StringVaultSecret(secretValue.toString());

269

}

270

271

return new EmptyVaultSecret();

272

273

} catch (Exception e) {

274

logger.error("Failed to retrieve secret: " + vaultSecretId, e);

275

return new EmptyVaultSecret();

276

}

277

}

278

279

@Override

280

public void close() {

281

if (vaultClient != null) {

282

vaultClient.close();

283

}

284

}

285

286

// Inner class for string secrets

287

private static class StringVaultSecret implements VaultStringSecret {

288

private final String secret;

289

private volatile boolean closed = false;

290

291

public StringVaultSecret(String secret) {

292

this.secret = secret;

293

}

294

295

@Override

296

public Optional<String> get() {

297

if (closed) {

298

throw new IllegalStateException("Secret has been closed");

299

}

300

return Optional.ofNullable(secret);

301

}

302

303

@Override

304

public void close() {

305

closed = true;

306

// Clear the secret from memory (not possible with String, but shown for demonstration)

307

}

308

}

309

310

// Inner class for empty secrets

311

private static class EmptyVaultSecret implements VaultStringSecret {

312

@Override

313

public Optional<String> get() {

314

return Optional.empty();

315

}

316

317

@Override

318

public void close() {

319

// Nothing to close

320

}

321

}

322

}

323

```

324

325

### Creating a Vault Provider Factory

326

327

```java

328

public class HashiCorpVaultProviderFactory implements VaultProviderFactory {

329

public static final String PROVIDER_ID = "hashicorp-vault";

330

331

private volatile VaultClient vaultClient;

332

333

@Override

334

public VaultProvider create(KeycloakSession session) {

335

if (vaultClient == null) {

336

synchronized (this) {

337

if (vaultClient == null) {

338

vaultClient = initializeVaultClient();

339

}

340

}

341

}

342

return new HashiCorpVaultProvider(vaultClient, getMountPath());

343

}

344

345

@Override

346

public void init(Config.Scope config) {

347

// Initialize configuration

348

}

349

350

@Override

351

public void postInit(KeycloakSessionFactory factory) {

352

// Post-initialization

353

}

354

355

@Override

356

public void close() {

357

if (vaultClient != null) {

358

vaultClient.close();

359

}

360

}

361

362

@Override

363

public String getId() {

364

return PROVIDER_ID;

365

}

366

367

@Override

368

public List<ProviderConfigProperty> getConfigMetadata() {

369

return Arrays.asList(

370

new ProviderConfigProperty("vault.url", "Vault URL", "HashiCorp Vault server URL",

371

ProviderConfigProperty.STRING_TYPE, "https://vault.example.com:8200"),

372

new ProviderConfigProperty("vault.token", "Vault Token", "Authentication token for Vault",

373

ProviderConfigProperty.PASSWORD, null),

374

new ProviderConfigProperty("vault.mount-path", "Mount Path", "Vault mount path for secrets",

375

ProviderConfigProperty.STRING_TYPE, "secret"),

376

new ProviderConfigProperty("vault.namespace", "Namespace", "Vault namespace (Enterprise feature)",

377

ProviderConfigProperty.STRING_TYPE, null),

378

new ProviderConfigProperty("vault.timeout", "Timeout", "Connection timeout in seconds",

379

ProviderConfigProperty.STRING_TYPE, "30")

380

);

381

}

382

383

private VaultClient initializeVaultClient() {

384

String vaultUrl = getConfig().get("vault.url", "https://vault.example.com:8200");

385

String vaultToken = getConfig().get("vault.token");

386

String namespace = getConfig().get("vault.namespace");

387

int timeout = getConfig().getInt("vault.timeout", 30);

388

389

VaultConfig vaultConfig = new VaultConfig()

390

.address(vaultUrl)

391

.token(vaultToken)

392

.openTimeout(timeout)

393

.readTimeout(timeout);

394

395

if (namespace != null && !namespace.isEmpty()) {

396

vaultConfig.nameSpace(namespace);

397

}

398

399

return new Vault(vaultConfig).logical();

400

}

401

402

private String getMountPath() {

403

return getConfig().get("vault.mount-path", "secret");

404

}

405

406

private Config.Scope getConfig() {

407

// Return configuration scope

408

return Config.scope("vault", PROVIDER_ID);

409

}

410

}

411

```

412

413

### Using Vault Transcriber

414

415

```java

416

// Using vault transcriber to resolve secrets in configuration

417

try (KeycloakSession session = sessionFactory.create()) {

418

VaultTranscriber transcriber = session.vault();

419

420

// Configuration values with vault references

421

String dbPassword = "${vault.secret/myapp/database}";

422

String apiKey = "${vault.api-keys/external-service}";

423

String certificateKeystore = "${vault.certificates/ssl-keystore}";

424

425

// Transcribe vault references to actual secret values

426

String actualDbPassword = transcriber.transcribe(dbPassword);

427

String actualApiKey = transcriber.transcribe(apiKey);

428

String actualKeystorePassword = transcriber.transcribe(certificateKeystore);

429

430

// Use the resolved secrets

431

DataSource dataSource = createDataSource("jdbc:postgresql://localhost/mydb",

432

"dbuser", actualDbPassword);

433

434

HttpClient httpClient = createHttpClient(actualApiKey);

435

436

KeyStore keyStore = loadKeyStore("keystore.p12", actualKeystorePassword);

437

}

438

```

439

440

### Direct Vault Provider Usage

441

442

```java

443

// Direct usage of vault provider

444

try (KeycloakSession session = sessionFactory.create()) {

445

VaultProvider vaultProvider = session.getProvider(VaultProvider.class);

446

447

// Retrieve database password

448

try (VaultStringSecret dbSecret = (VaultStringSecret) vaultProvider.obtainSecret("secret/myapp/database")) {

449

String dbPassword = dbSecret.getOrDefault("defaultPassword");

450

451

// Use the password

452

Connection connection = DriverManager.getConnection(

453

"jdbc:postgresql://localhost/mydb", "dbuser", dbPassword);

454

}

455

456

// Retrieve API key

457

try (VaultStringSecret apiSecret = (VaultStringSecret) vaultProvider.obtainSecret("api-keys/payment-gateway")) {

458

String apiKey = apiSecret.get().orElse(null);

459

if (apiKey != null) {

460

PaymentGatewayClient client = new PaymentGatewayClient(apiKey);

461

}

462

}

463

464

// Retrieve certificate data

465

try (VaultRawSecret certSecret = vaultProvider.obtainSecret("certificates/client-cert")) {

466

byte[] certData = certSecret.getOrDefault(new byte[0]);

467

if (certData.length > 0) {

468

X509Certificate certificate = loadCertificate(certData);

469

}

470

}

471

}

472

```

473

474

### Custom Vault Key Resolver

475

476

```java

477

public class CustomVaultKeyResolver implements VaultKeyResolver {

478

private static final Pattern VAULT_PATTERN = Pattern.compile("\\$\\{vault\\.([^}]+)\\}");

479

private static final String VAULT_PREFIX = "${vault.";

480

private static final String VAULT_SUFFIX = "}";

481

482

@Override

483

public boolean isVaultKey(String value) {

484

return value != null && value.startsWith(VAULT_PREFIX) && value.endsWith(VAULT_SUFFIX);

485

}

486

487

@Override

488

public String getVaultKey(String value) {

489

if (!isVaultKey(value)) {

490

return null;

491

}

492

493

Matcher matcher = VAULT_PATTERN.matcher(value);

494

if (matcher.matches()) {

495

return matcher.group(1);

496

}

497

498

return null;

499

}

500

501

@Override

502

public String createVaultReference(String key) {

503

if (key == null || key.isEmpty()) {

504

throw new IllegalArgumentException("Vault key cannot be null or empty");

505

}

506

507

return VAULT_PREFIX + key + VAULT_SUFFIX;

508

}

509

}

510

```

511

512

### Environment-Specific Vault Configuration

513

514

```java

515

// Configure vault provider based on environment

516

public class EnvironmentVaultProviderFactory implements VaultProviderFactory {

517

public static final String PROVIDER_ID = "environment-vault";

518

519

@Override

520

public VaultProvider create(KeycloakSession session) {

521

String environment = System.getProperty("keycloak.environment", "development");

522

523

switch (environment.toLowerCase()) {

524

case "production":

525

return createProductionVaultProvider();

526

case "staging":

527

return createStagingVaultProvider();

528

case "development":

529

default:

530

return createDevelopmentVaultProvider();

531

}

532

}

533

534

private VaultProvider createProductionVaultProvider() {

535

// Use HashiCorp Vault for production

536

return new HashiCorpVaultProvider(

537

createVaultClient("https://vault.prod.example.com:8200"),

538

"prod-secrets"

539

);

540

}

541

542

private VaultProvider createStagingVaultProvider() {

543

// Use HashiCorp Vault for staging

544

return new HashiCorpVaultProvider(

545

createVaultClient("https://vault.staging.example.com:8200"),

546

"staging-secrets"

547

);

548

}

549

550

private VaultProvider createDevelopmentVaultProvider() {

551

// Use file-based vault for development

552

return new FileBasedVaultProvider("/etc/keycloak/dev-secrets");

553

}

554

555

@Override

556

public String getId() {

557

return PROVIDER_ID;

558

}

559

}

560

561

// Simple file-based vault for development

562

public class FileBasedVaultProvider implements VaultProvider {

563

private final Path secretsDirectory;

564

565

public FileBasedVaultProvider(String secretsPath) {

566

this.secretsDirectory = Paths.get(secretsPath);

567

}

568

569

@Override

570

public VaultRawSecret obtainSecret(String vaultSecretId) {

571

try {

572

Path secretFile = secretsDirectory.resolve(vaultSecretId + ".txt");

573

if (!Files.exists(secretFile)) {

574

return new EmptyVaultSecret();

575

}

576

577

String content = Files.readString(secretFile, StandardCharsets.UTF_8).trim();

578

return new StringVaultSecret(content);

579

580

} catch (IOException e) {

581

logger.error("Failed to read secret file: " + vaultSecretId, e);

582

return new EmptyVaultSecret();

583

}

584

}

585

586

@Override

587

public void close() {

588

// Nothing to close for file-based implementation

589

}

590

}

591

```

592

593

### Vault Secret Caching

594

595

```java

596

// Vault provider with caching for better performance

597

public class CachedVaultProvider implements VaultProvider {

598

private final VaultProvider delegate;

599

private final Cache<String, VaultRawSecret> secretCache;

600

private final Duration cacheExpiration;

601

602

public CachedVaultProvider(VaultProvider delegate, Duration cacheExpiration) {

603

this.delegate = delegate;

604

this.cacheExpiration = cacheExpiration;

605

this.secretCache = Caffeine.newBuilder()

606

.expireAfterWrite(cacheExpiration)

607

.maximumSize(1000)

608

.removalListener((key, value, cause) -> {

609

if (value instanceof VaultRawSecret) {

610

((VaultRawSecret) value).close();

611

}

612

})

613

.build();

614

}

615

616

@Override

617

public VaultRawSecret obtainSecret(String vaultSecretId) {

618

return secretCache.get(vaultSecretId, key -> {

619

VaultRawSecret secret = delegate.obtainSecret(key);

620

621

// Convert to cacheable secret

622

if (secret instanceof VaultStringSecret) {

623

VaultStringSecret stringSecret = (VaultStringSecret) secret;

624

String value = stringSecret.getOrDefault(null);

625

secret.close(); // Close original

626

627

return value != null ? new CacheableStringSecret(value) : new EmptyVaultSecret();

628

}

629

630

return secret;

631

});

632

}

633

634

@Override

635

public void close() {

636

secretCache.invalidateAll();

637

delegate.close();

638

}

639

640

private static class CacheableStringSecret implements VaultStringSecret {

641

private final String value;

642

643

public CacheableStringSecret(String value) {

644

this.value = value;

645

}

646

647

@Override

648

public Optional<String> get() {

649

return Optional.ofNullable(value);

650

}

651

652

@Override

653

public void close() {

654

// Nothing to close for cached values

655

}

656

}

657

}

658

```