Skip to content

Secret Management - Güvenli Konfigürasyon Yönetimi

Modern uygulamalarda gizli bilgilerin (secret) güvenli yönetimi kritik öneme sahiptir. Bu bölümde Spring Boot uygulamalarında HashiCorp Vault entegrasyonu, şifrelenmiş özellikler ve bulut tabanlı gizli bilgi yönetimi çözümlerini ele alacağız.

HashiCorp Vault Entegrasyonu (Spring Boot)

Spring Cloud Vault Konfigürasyonu

Spring Cloud Vault, uygulamaların Vault'tan otomatik olarak gizli bilgileri almasını sağlar:

yaml
spring:
  cloud:
    vault:
      host: vault.example.com
      port: 8200
      scheme: https
      authentication: TOKEN
      token: ${VAULT_TOKEN}
      kv:
        enabled: true
        backend: secret
        application-name: myapp
      generic:
        enabled: true
        backend: secret
        default-context: myapp
        application-name: myapp

Otomatik Gizli Bilgi Enjeksiyonu

java
@Configuration
@EnableConfigurationProperties
public class VaultConfiguration {
    
    @Value("${database.password}")
    private String databasePassword;
    
    @Value("${api.secret}")
    private String apiSecret;
    
    // Vault'tan otomatik olarak alınan değerler
}

Dinamik Konfigürasyon Yenileme

java
@Component
@RefreshScope
public class DynamicConfiguration {
    
    @Value("${dynamic.secret}")
    private String dynamicSecret;
    
    public String getSecret() {
        return dynamicSecret;
    }
}

Vault Kimlik Doğrulama Yöntemleri

AppRole Authentication

java
@Configuration
public class VaultConfig {
    
    @Bean
    public VaultTemplate vaultTemplate() {
        VaultEndpoint vaultEndpoint = new VaultEndpoint();
        vaultEndpoint.setHost("vault.example.com");
        vaultEndpoint.setPort(8200);
        vaultEndpoint.setScheme("https");
        
        AppRoleAuthentication authentication = new AppRoleAuthentication(
            AppRoleAuthenticationOptions.builder()
                .roleId(RoleId.provided("my-role-id"))
                .secretId(SecretId.provided("my-secret-id"))
                .build()
        );
        
        return new VaultTemplate(vaultEndpoint, authentication);
    }
}

Kubernetes Authentication

java
@Configuration
public class KubernetesVaultConfig {
    
    @Bean
    public VaultTemplate vaultTemplate() {
        VaultEndpoint vaultEndpoint = VaultEndpoint.create(
            "vault.example.com", 8200);
        
        KubernetesAuthentication authentication = 
            new KubernetesAuthentication(
                KubernetesAuthenticationOptions.builder()
                    .role("my-k8s-role")
                    .jwtSupplier(() -> {
                        try {
                            return Files.readString(
                                Paths.get("/var/run/secrets/kubernetes.io/serviceaccount/token"));
                        } catch (IOException e) {
                            throw new VaultException("JWT token okunamadı", e);
                        }
                    })
                    .build()
            );
        
        return new VaultTemplate(vaultEndpoint, authentication);
    }
}

AWS IAM Authentication

java
@Configuration
public class AwsIamVaultConfig {
    
    @Bean
    public VaultTemplate vaultTemplate() {
        VaultEndpoint vaultEndpoint = VaultEndpoint.create(
            "vault.example.com", 8200);
        
        AwsIamAuthentication authentication = new AwsIamAuthentication(
            AwsIamAuthenticationOptions.builder()
                .role("my-aws-role")
                .credentialsProvider(DefaultCredentialsProvider.create())
                .build()
        );
        
        return new VaultTemplate(vaultEndpoint, authentication);
    }
}

Spring Boot Konfigürasyon Güvenliği

Jasypt ile Şifrelenmiş Özellikler

xml
<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.4</version>
</dependency>
yaml
jasypt:
  encryptor:
    password: ${JASYPT_PASSWORD}
    algorithm: PBEWITHHMACSHA512ANDAES_256
    key-obtention-iterations: 1000
    pool-size: 1
    provider-name: SunJCE
    salt-generator-classname: org.jasypt.salt.RandomSaltGenerator
    iv-generator-classname: org.jasypt.iv.RandomIvGenerator
    string-output-type: base64

# Şifrelenmiş değerler
database:
  password: ENC(encrypted_password_here)
  
api:
  secret: ENC(encrypted_api_secret_here)

Konfigürasyon Şifreleme Utility

java
@Component
public class ConfigurationEncryptor {
    
    private final StringEncryptor encryptor;
    
    public ConfigurationEncryptor(StringEncryptor encryptor) {
        this.encryptor = encryptor;
    }
    
    public String encrypt(String plainText) {
        return encryptor.encrypt(plainText);
    }
    
    public String decrypt(String encryptedText) {
        return encryptor.decrypt(encryptedText);
    }
}

Çevre Değişkeni Tabanlı Şifreleme

java
@Configuration
public class EncryptionConfig {
    
    @Bean("jasyptStringEncryptor")
    public StringEncryptor stringEncryptor() {
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        
        config.setPassword(getEncryptionPassword());
        config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
        config.setStringOutputType("base64");
        
        encryptor.setConfig(config);
        return encryptor;
    }
    
    private String getEncryptionPassword() {
        String password = System.getenv("JASYPT_ENCRYPTOR_PASSWORD");
        if (password == null) {
            throw new IllegalStateException("JASYPT_ENCRYPTOR_PASSWORD çevre değişkeni tanımlı değil");
        }
        return password;
    }
}

Veritabanı Güvenlik Patterns

Bağlantı Güvenliği

yaml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/mydb?ssl=true&sslmode=verify-full
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    hikari:
      connection-test-query: SELECT 1
      validation-timeout: 3000
      leak-detection-threshold: 60000
      maximum-pool-size: 20
      minimum-idle: 5
    properties:
      ssl: true
      sslmode: verify-full
      sslcert: ${SSL_CERT_PATH}
      sslkey: ${SSL_KEY_PATH}
      sslrootcert: ${SSL_ROOT_CERT_PATH}

Veritabanı Şifreleme

java
@Entity
@Table(name = "secure_data")
public class SecureData {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Convert(converter = EncryptedStringConverter.class)
    @Column(name = "sensitive_data")
    private String sensitiveData;
    
    @Convert(converter = EncryptedStringConverter.class)
    @Column(name = "personal_info")
    private String personalInfo;
    
    // getters and setters
}

JPA Attribute Converter

java
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
    
    private final AESUtil aesUtil;
    
    public EncryptedStringConverter() {
        this.aesUtil = new AESUtil(getEncryptionKey());
    }
    
    @Override
    public String convertToDatabaseColumn(String attribute) {
        if (attribute == null) {
            return null;
        }
        try {
            return aesUtil.encrypt(attribute);
        } catch (Exception e) {
            throw new RuntimeException("Şifreleme hatası", e);
        }
    }
    
    @Override
    public String convertToEntityAttribute(String dbData) {
        if (dbData == null) {
            return null;
        }
        try {
            return aesUtil.decrypt(dbData);
        } catch (Exception e) {
            throw new RuntimeException("Şifre çözme hatası", e);
        }
    }
    
    private String getEncryptionKey() {
        return System.getenv("DB_ENCRYPTION_KEY");
    }
}

Anahtar Yönetimi Best Practices

Şifreleme at Rest

java
@Service
public class FileEncryptionService {
    
    private final AESUtil aesUtil;
    
    public FileEncryptionService() {
        this.aesUtil = new AESUtil(getFileEncryptionKey());
    }
    
    public void encryptFile(String inputPath, String outputPath) throws Exception {
        byte[] fileContent = Files.readAllBytes(Paths.get(inputPath));
        byte[] encryptedContent = aesUtil.encrypt(fileContent);
        Files.write(Paths.get(outputPath), encryptedContent);
    }
    
    public void decryptFile(String inputPath, String outputPath) throws Exception {
        byte[] encryptedContent = Files.readAllBytes(Paths.get(inputPath));
        byte[] decryptedContent = aesUtil.decrypt(encryptedContent);
        Files.write(Paths.get(outputPath), decryptedContent);
    }
    
    private String getFileEncryptionKey() {
        return System.getenv("FILE_ENCRYPTION_KEY");
    }
}

Backup Şifreleme

java
@Service
public class BackupEncryptionService {
    
    private final StringEncryptor encryptor;
    
    public BackupEncryptionService(StringEncryptor encryptor) {
        this.encryptor = encryptor;
    }
    
    public void createEncryptedBackup(String data, String backupPath) throws IOException {
        String encryptedData = encryptor.encrypt(data);
        Files.write(Paths.get(backupPath), encryptedData.getBytes());
    }
    
    public String restoreFromEncryptedBackup(String backupPath) throws IOException {
        String encryptedData = Files.readString(Paths.get(backupPath));
        return encryptor.decrypt(encryptedData);
    }
}

Cloud-Native Secret Management

AWS Secrets Manager

xml
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>secretsmanager</artifactId>
</dependency>
java
@Configuration
public class AwsSecretsConfig {
    
    @Bean
    public SecretsManagerClient secretsManagerClient() {
        return SecretsManagerClient.builder()
            .region(Region.US_WEST_2)
            .credentialsProvider(DefaultCredentialsProvider.create())
            .build();
    }
}

AWS Secrets Manager Service

java
@Service
public class AwsSecretsManagerService {
    
    private final SecretsManagerClient secretsClient;
    
    public AwsSecretsManagerService(SecretsManagerClient secretsClient) {
        this.secretsClient = secretsClient;
    }
    
    public String getSecret(String secretName) {
        try {
            GetSecretValueRequest request = GetSecretValueRequest.builder()
                .secretId(secretName)
                .build();
            
            GetSecretValueResponse response = secretsClient.getSecretValue(request);
            return response.secretString();
        } catch (SecretsManagerException e) {
            throw new RuntimeException("Secret alınamadı: " + secretName, e);
        }
    }
    
    public void updateSecret(String secretName, String secretValue) {
        try {
            UpdateSecretRequest request = UpdateSecretRequest.builder()
                .secretId(secretName)
                .secretString(secretValue)
                .build();
            
            secretsClient.updateSecret(request);
        } catch (SecretsManagerException e) {
            throw new RuntimeException("Secret güncellenemedi: " + secretName, e);
        }
    }
    
    public void rotateSecret(String secretName) {
        try {
            RotateSecretRequest request = RotateSecretRequest.builder()
                .secretId(secretName)
                .build();
            
            secretsClient.rotateSecret(request);
        } catch (SecretsManagerException e) {
            throw new RuntimeException("Secret rotate edilemedi: " + secretName, e);
        }
    }
}

Kubernetes Secrets

yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: production
type: Opaque
data:
  database-password: <base64-encoded-password>
  api-key: <base64-encoded-api-key>
  jwt-secret: <base64-encoded-jwt-secret>

External Secrets Operator

yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: production
spec:
  provider:
    vault:
      server: "https://vault.example.com:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "my-role"
          serviceAccountRef:
            name: "external-secrets"
yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
  namespace: production
spec:
  refreshInterval: 30s
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: app-secrets
    creationPolicy: Owner
  data:
  - secretKey: database-password
    remoteRef:
      key: secret/app
      property: database-password
  - secretKey: api-key
    remoteRef:
      key: secret/app
      property: api-key

Sealed Secrets

yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: mysecret
  namespace: production
spec:
  encryptedData:
    database-password: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEQAx...
  template:
    metadata:
      name: mysecret
      namespace: production
    type: Opaque

Secret Management Best Practices

Secret Rotation Strategy

java
@Component
@Scheduled
public class SecretRotationService {
    
    private final VaultTemplate vaultTemplate;
    private final ApplicationEventPublisher eventPublisher;
    
    public SecretRotationService(VaultTemplate vaultTemplate, 
                               ApplicationEventPublisher eventPublisher) {
        this.vaultTemplate = vaultTemplate;
        this.eventPublisher = eventPublisher;
    }
    
    @Scheduled(cron = "0 0 2 * * ?") // Her gün saat 02:00
    public void rotateSecrets() {
        try {
            // Database password rotation
            rotateDatabasePassword();
            
            // API key rotation
            rotateApiKeys();
            
            // JWT secret rotation
            rotateJwtSecret();
            
            eventPublisher.publishEvent(new SecretsRotatedEvent());
        } catch (Exception e) {
            log.error("Secret rotation başarısız", e);
            eventPublisher.publishEvent(new SecretRotationFailedEvent(e));
        }
    }
    
    private void rotateDatabasePassword() {
        String newPassword = generateSecurePassword();
        vaultTemplate.write("secret/database", Map.of("password", newPassword));
        log.info("Database password rotated");
    }
    
    private void rotateApiKeys() {
        String newApiKey = generateApiKey();
        vaultTemplate.write("secret/api", Map.of("key", newApiKey));
        log.info("API key rotated");
    }
    
    private void rotateJwtSecret() {
        String newJwtSecret = generateJwtSecret();
        vaultTemplate.write("secret/jwt", Map.of("secret", newJwtSecret));
        log.info("JWT secret rotated");
    }
    
    private String generateSecurePassword() {
        // Güvenli password generation logic
        return SecureRandom.getInstanceStrong()
            .ints(32, 33, 126)
            .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
            .toString();
    }
}

Secret Validation

java
@Component
public class SecretValidator {
    
    public boolean validateDatabasePassword(String password) {
        return password != null && 
               password.length() >= 12 &&
               password.matches(".*[A-Z].*") &&
               password.matches(".*[a-z].*") &&
               password.matches(".*[0-9].*") &&
               password.matches(".*[!@#$%^&*()].*");
    }
    
    public boolean validateApiKey(String apiKey) {
        return apiKey != null && 
               apiKey.length() == 32 &&
               apiKey.matches("^[A-Za-z0-9]+$");
    }
    
    public boolean validateJwtSecret(String jwtSecret) {
        return jwtSecret != null && 
               jwtSecret.length() >= 64;
    }
}

Bu kapsamlı secret management implementasyonu, modern Spring Boot uygulamalarında gizli bilgilerin güvenli yönetimi için gerekli tüm bileşenleri içermektedir.

Eren Demir tarafından oluşturulmuştur.