728x90

개요

기존에 Jasypt 패키지를 이용해 YML 설정 파일 암호화를 진행하였지만 application.yml 파일 내 jasypt.encryptor.password가 노출되어있어 암호화는 해주었지만 암호화를 풀 수 있는 열쇠를 노출해 이와 같은 문제를 해결하는 방법으로 Github Repository secrets을 이용해 해결하였습니다.

떠오른 방법

  1. 암호화를 풀 수 있는 키를 우분투 서버 자체에 환경변수로 등록하여서 도커 컨테이너에 스프링 run 할 때 환경변수를 전달한다.
  2. Github Repository secrets를 이용한다.
  3. Spring Vault를 이용한다.

Spring Vault란?

Vault는 HashiCorp에 의해서 개발된 크로스 플랫폼 패스워드 및 인증 관리 시스템이다. 공개되면 안 되는 비밀번호, API 키, 토큰 등을 저장하고 관리한다.

 

선택한 방법

우선 저는 제일 간단한 방법으로 진행하기 위해 2번을 선택했습니다.

시간이 되면 1번 방법과 3번 방법으로 진행해 보겠습니다.

 

해결 방법

1. GitHub 해당 Repository에 들어가서 Settings에서 왼쪽 하단에 있는 Secrets의 Actions에 접속한다.

2. Secrets의 Actions에서 오른쪽 상단에 있는 New repository secret을 선택한다.

3. secret의 Name을 입력하고 Secret에는 암호화를 진행하고 싶은 내용을 입력하고 Add secret을 누르면 생성된다.

4. 현재 제가 만든 secret 목록입니다.

5. .github/workflows/gradle.yml 파일에 아래의 코드를 추가한다.

Repository secrets을 사용하면 GitHub Actions의 gradle.yml에만 환경변수가 적용되어 스프링에서 사용할 수 있게 변수를 넘겨주어야 한다.

- name: Set Yaml
  uses: microsoft/variable-substitution@v1
  with:
    files: ./src/main/resources/application-prod.yml 
  env:
    spring.datasource.url: ${{ secrets.DB_URL }} 
    spring.datasource.username: ${{ secrets.DB_USERNAME }} 
    spring.datasource.password: ${{ secrets.DB_PASSWORD }}

gradle.yml 전체 코드

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: <https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle>
name: Java CI with Gradle

on:
  push:
    branches: [ "main" ]
#   pull_request:
#       branches: [ "main" ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'
    - name: Set Yaml
      uses: microsoft/variable-substitution@v1
      with:
        files: ./src/main/resources/application-prod.yml 
      env:
        spring.datasource.url: ${{ secrets.DB_URL }} 
        spring.datasource.username: ${{ secrets.DB_USERNAME }} 
        spring.datasource.password: ${{ secrets.DB_PASSWORD }} 
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
    - name: Build with Gradle
      run: ./gradlew build
    - name: Docker build
      run: |
       docker login -u ${{ secrets.USERNAME }} -p ${{ secrets.PASSWORD }}
       docker build -t spring-cicd2 .
       docker tag spring-cicd2 lusida0131/spring-cicd2:latest
       docker push lusida0131/spring-cicd2:latest
  deploy:
    needs: build  # build 후에 실행되도록 정의
    name: Deploy
    runs-on: [ self-hosted, label-go ] # AWS ./configure에서 사용할 label명
    steps:
      # 3000 -> 80 포트로 수행하도록 지정
      - name: Docker run
        run: |
          docker login -u ${{ secrets.USERNAME }} -p ${{ secrets.PASSWORD }}
          docker stop spring-cicd2 && docker rm spring-cicd2 && docker rmi lusida0131/spring-cicd2:latest
          docker run -d -p 8081:8081 --name spring-cicd2 --restart always lusida0131/spring-cicd2:latest

변수는 ${{ secrets.적용한이름 }}으로 불러올 수 있다. 위의 코드를 보면 ./src/main/resources/application-prod.yml의 파일에 spring.datasource.url를 ${{ secrets.DB_URL }} 변수로 덮어 씌운다. 실제 application-prod.yml의 값은 임의의 값을 주어도 상관없다.

datasource:
    url: ${url}
    username: ${username}
    password: ${password}
728x90
728x90

개요

GitHub의 프로젝트 레파지토리에 application.yml이나 application.properties 파일에 DB의 유저 정보와 비밀번호 또는 키값을 명시해두는 경우가 있습니다. 또한 yml이나 properties을. gitignore에 추가할 경우 aws에서 프로젝트를 실행하는 경우나 docker로 실행하는 경우 이와 같은 오류가 발생한다. "Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured. Reason: Failed to determine a suitable driver class” 위의 오류는 database 설정 파일을 찾지 못해서 나는 오류이다. 하지만 DB 정보를 명시해두는 경우 GitHub 특성상 오픈 소스이므로 정보가 노출되어 보안에 심각한 문제를 초래할 수 있다는 점입니다. 위와 같은 사항들을 해결하기 위해 보안에 민감한 값들을 암호화시켜 저장해야 한다.

 

암호화 방식

YML파일 암호화 방법으로는 여러 방법이 있지만 저는 Java의 Jasypt(Java Simplified Encryption) 패키지를 사용하여 설정 파일들의 암호화를 진행하겠습니다.

 

장점

Jasypt를 설정하려면 여러 코드들이 추가되지만 로컬,배포 환경의 설정 파일을 동일하게 사용할 수 있고 설정 파일이 외부 유출되어도 암호화되어있기에 비교적 안전하다는 장점이 있다.


암호화 진행 순서

1. build.gradle파일에 jasypt implementation을 작성한다.

// yml 설정 파일 암호화
implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.3'

 

2. jasyptStringEncryptor로 Bean을 등록합니다. 이 이름은 application.yml의 jasypt bean으로 등록할 때 사용합니다. private String PASSWORD는 yml 설정 파일에서 jasypt.encryptor.password으로 읽어서 가져옵니다.

import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JasyptConfig {

		@Value("${jasypt.encryptor.password}")
    private String PASSWORD;

    @Bean(name = "jasyptStringEncryptor")
    public StringEncryptor stringEncryptor() {
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        config.setPassword(PASSWORD); // 암호화할 때 사용하는 키
        config.setAlgorithm("PBEWithMD5AndDES"); // 암호화 알고리즘
        config.setKeyObtentionIterations("1000"); // 반복할 해싱 회수
        config.setPoolSize("1"); // 인스턴스 pool
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); // salt 생성 클래스
        config.setStringOutputType("base64"); //인코딩 방식
        encryptor.setConfig(config);
        return encryptor;
    }
}

 

3. application.yml에 암호화된 값을 적어주기 전에 미리 해당 값들의 암호화된 값을 알기위해 Test코드를 작성해서 출력한다.

import org.assertj.core.api.Assertions;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.junit.jupiter.api.Test;

class JasyptConfigTest {

    @Test
    void jasypt(){
        String url = "자신의 DB URL";
        String username = "자신의 DB USER";
        String password = "자신의 DB PASSWORD";

        String encryptUrl = jasyptEncrypt(url);
        String encryptUsername = jasyptEncrypt(username);
        String encryptPassword = jasyptEncrypt(password);

        System.out.println("encryptUrl : " + encryptUrl);
        System.out.println("encryptUsername : " + encryptUsername);
        System.out.println("encryptPassword : " + encryptPassword);

        Assertions.assertThat(url).isEqualTo(jasyptDecryt(encryptUrl));
    }

    private String jasyptEncrypt(String input) {
        String key = "암호화에 쓰일 키값";
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        encryptor.setAlgorithm("PBEWithMD5AndDES");
        encryptor.setPassword(key);
        return encryptor.encrypt(input);
    }

    private String jasyptDecryt(String input){
        String key = "복호화에 쓰일 키값";
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        encryptor.setAlgorithm("PBEWithMD5AndDES");
        encryptor.setPassword(key);
        return encryptor.decrypt(input);
    }
}

 

4. 위의 TestCode를 작성하면 암호화된 값이 출력된다.

 

5. application.yml에 암호화된 값과 jasyptStringEncryptor bean 설정

spring:
  datasource:
    url: ENC(o9FcC1LtWYlFw88yy/5Ilg==)
    username: ENC(cp/1/Ok8sPGkYEJi27Jknw==)
    password: ENC(9vlmkROOsAgMTk3rFkbiQxXFwFaQoew0)
jasypt:
  encryptor:
    bean: jasyptStringEncryptor
    password : 1234

 

jasyptStringEncryptor를 jasypt bean으로 등록하고, 각 속성값에 ENC( 암호화 값 ) 형식으로 입력해줍니다. 위와 같은 과정을 통해 application.yml 파일의 암호화를 설정해 주었습니다. 하지만, application.yml 파일 내 jasypt.encryptor.password가 노출되어있다면, 암호화는 해주었지만 암호화를 풀 수 있는 열쇠를 지어준 꼴이 된다. 이와 같은 문제를 해결하는 방법은 다음에 작성해 보겠습니다.

728x90

+ Recent posts