0) 현재 remote가 HTTPS인지 확인

 
git remote -v
  • https://bitbucket.org/... 로 보이면 이 방식 그대로 진행하면 됩니다.
  • git@bitbucket.org:... 면 이미 SSH라서 1번 필요 없음.

1) Bitbucket(Atlassian)에서 API token 생성

  • Atlassian account → SecurityAPI tokens에서 생성
  • 스코프는 최소로(예: repository write/read 등 “push 가능한 권한”)

생성 직후 토큰 문자열은 한 번만 보여주는 경우가 많아서 바로 안전한 곳에 저장해두세요.


2) macOS에 저장된 “예전 Bitbucket 비밀번호(app password)”를 지우기

이 단계가 핵심이에요.
안 지우면 Git이 계속 옛날 자격증명으로 자동 시도해서 새 토큰을 입력할 기회가 안 옵니다.

아래 (A) GUI 또는 (B) 터미널 중 하나만 해도 됩니다.


✅ (A) Keychain Access에서 삭제하는 법 (GUI, 자세히)

  1. Keychain Access(키체인 접근) 실행
  • Spotlight(⌘ + Space) → Keychain Access 검색 → 실행
  1. 왼쪽 사이드바에서
  • Keychains: login 선택
  • Category: Passwords 선택 (또는 “All Items”)
  1. 오른쪽 위 검색창에 아래를 하나씩 검색
  1. 보통 이런 이름/형태가 잡힙니다
  • Internet password: bitbucket.org
  • Application password 또는 git 관련 항목
  • 계정(Account) 컬럼에 이메일/username이 보이기도 함
  1. 삭제
  • 해당 항목 선택 → 우클릭 → Delete “…”
    또는 키보드 Delete
  • macOS 비밀번호(터치ID) 요구하면 승인

✅ 여기까지 하면 “Bitbucket에 저장된 자격증명”이 제거됩니다.


✅ (B) 터미널로 Keychain에서 삭제하는 법 (정확/빠름)

1) 먼저 Keychain에 뭐가 저장돼 있는지 확인

 
security find-internet-password -s bitbucket.org
  • 뭐가 나오면 저장된 상태
  • could not be found면 저장된 게 없는 상태

2) 삭제 (가장 일반적인 케이스)

 
security delete-internet-password -s bitbucket.org

만약 계정(이메일/username)까지 지정해서 지우고 싶으면:

 
security delete-internet-password -s bitbucket.org -a "YOUR_EMAIL_OR_USERNAME"

3) Git 자체가 기억하는 credential도 같이 “거부” 처리 (선택이지만 추천)

 
git credential reject <<EOF protocol=https host=bitbucket.org EOF

3) 다시 push 해서 새 토큰 입력

이제 push를 하면 인증 창/프롬프트가 다시 뜹니다.

 
git push

입력:

  • Username: 보통 Bitbucket/Atlassian 계정 이메일 또는 username
  • Password: API token (새로 만든 것)

✅ 추가 팁: “토큰을 URL에 박아넣기”는 비추

가끔 https://user:token@bitbucket.org/... 같은 방식이 있는데,

  • 히스토리/로그/툴에 남을 수 있어 보안상 비추천입니다.

🧪 문제 해결 체크

“삭제했는데도 계속 옛날 걸로 인증 시도”하면

  1. git remote -v가 진짜 bitbucket.org 맞는지 확인
  2. Keychain에 bitbucket 검색했을 때 항목이 남아있는지 재확인
  3. 어떤 credential helper 쓰는지 확인:
 
git config --global --get credential.helper

macOS면 보통 osxkeychain이 정상입니다.

Posted by yongary
,

리플렉션 어노테이션 방식 (이전글 2.2 참조)로 
mongoDB 필드를 암호화하면, mongoTemplate.save에는 적용되지만, mongoTemplate.update에는 반영되지 않는다.

이걸 우회하는 방법으로 새로운 암호화전용 mongoTemplate을 만들 수 있는데

@Component
public class EncryptingMongoTemplate {

    private final MongoTemplate mongoTemplate;
    private final IpoCreditMasterService cryptoService;

    // 캐시: 엔티티별 암호화 필드명 목록
    private final ConcurrentMap<Class<?>, Set<String>> encryptedFieldCache = new ConcurrentHashMap<>();

    public EncryptingMongoTemplate(MongoTemplate mongoTemplate,
                                   IpoCreditMasterService cryptoService) {
        this.mongoTemplate = mongoTemplate;
        this.cryptoService = cryptoService;
    }

    // === 외부에서 이 메서드들을 써서 update === //

    public UpdateResult updateFirst(Query query, Update update, Class<?> entityClass) {
        encryptUpdateFields(entityClass, update);
        return mongoTemplate.updateFirst(query, update, entityClass);
    }

    public UpdateResult updateMulti(Query query, Update update, Class<?> entityClass) {
        encryptUpdateFields(entityClass, update);
        return mongoTemplate.updateMulti(query, update, entityClass);
    }

    public UpdateResult upsert(Query query, Update update, Class<?> entityClass) {
        encryptUpdateFields(entityClass, update);
        return mongoTemplate.upsert(query, update, entityClass);
    }

    // 필요하면 find, save 등도 래핑해서 여기에 모아줄 수 있음

    // === 핵심: Update 안의 @EncryptedOrNotText 필드 값 암호화 === //

    private void encryptUpdateFields(Class<?> entityClass, Update update) {
        if (update == null) return;

        Set<String> encryptedFields = getEncryptedFieldNames(entityClass);
        if (encryptedFields.isEmpty()) return;

        // Spring Data Mongo의 Update는 내부에 Document를 가지고 있음
        Document root = update.getUpdateObject();

        // 여기서는 .set()만 쓴다고 하셨으니 $set만 처리
        Document setDoc = (Document) root.get("$set");
        if (setDoc == null) {
            return;
        }

        for (String fieldName : encryptedFields) {
            if (!setDoc.containsKey(fieldName)) continue;

            Object raw = setDoc.get(fieldName);
            if (raw instanceof String str) {
                setDoc.put(fieldName, cryptoService.encrypt(str));
            }
        }
    }

    private Set<String> getEncryptedFieldNames(Class<?> entityClass) {
        return encryptedFieldCache.computeIfAbsent(entityClass, this::scanEncryptedFields);
    }

    private Set<String> scanEncryptedFields(Class<?> type) {
        Set<String> result = new HashSet<>();
        Class<?> current = type;
        while (current != null && current != Object.class) {
            for (Field field : current.getDeclaredFields()) {
                if (field.isAnnotationPresent(EncryptedOrNotText.class)
                        && field.getType() == String.class) {
                    // @Field("otherName") 고려 안 한 버전
                    result.add(field.getName());
                }
            }
            current = current.getSuperclass();
        }
        return result;
    }
}

 

 

사용예제:

public class UserService {

    private final EncryptingMongoTemplate encryptMt;

    public void updateUserPhone(String userId, String newPhone) {
        Query q = Query.query(Criteria.where("_id").is(userId));
        Update u = new Update()
                .set("phone", newPhone); // phone이 @EncryptedOrNotText

        encryptMt.updateFirst(q, u, UserEntity.class);
        // → 호출 전에 EncryptingMongoTemplate이 newPhone을 encrypt해서 MongoDB에 저장
    }
}
Posted by yongary
,

1. Postgres + JPA: AttributeConverter 예시 (OK)

import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;

@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {

    @Override
    public String convertToDatabaseColumn(String attribute) {
        if (attribute == null) return null;
        return IpoCreditMasterService.encrypt(attribute);
    }

    @Override
    public String convertToEntityAttribute(String dbData) {
        if (dbData == null) return null;
        try {
            return IpoCreditMasterService.decrypt(dbData);
        } catch (Exception e) {
            // 섞인 데이터 방어
            return dbData;
        }
    }
}

 

 

@Entity
public class EncTest {

    @Id
    private String 계약계정;

    private int yymm;

    @Convert(converter = EncryptedStringConverter.class)
    private String 고객이름;
}

 

 

2. mongoDB 에는 위 방식은 적용안되고, 유사방식 추천: Mongo용 Converter 두 개

(이것도 필요없음: 아래 2-2 방식이 더 좋음)

import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;

@WritingConverter
public class EncryptedStringWritingConverter implements Converter<String, String> {
    @Override
    public String convert(String source) {
        if (source == null) return null;
        return CryptoUtil.encrypt(source);
    }
}

@ReadingConverter
public class EncryptedStringReadingConverter implements Converter<String, String> {
    @Override
    public String convert(String source) {
        if (source == null) return null;
        try {
            return CryptoUtil.decrypt(source);
        } catch (Exception e) {
            // 섞인 데이터 방어
            return source;
        }
    }
}


2-1 mongoDB 설정 변경

(이건 전체 설정용이므로, 필드 설정에선 필요없음)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;

import java.util.List;

//@Configuration
//public class MongoConfig {

//    @Bean
//    public MongoCustomConversions mongoCustomConversions() {
        return new MongoCustomConversions(List.of(
                new EncryptedStringWritingConverter(),
                new EncryptedStringReadingConverter()
        ));
    }
//}

 

2-2 위는 모든 필드 적용이므로, mongoDB 특정필드에만 적용 방식
  (A: 어노테이션 하나 추가)

package com.vwsai.project2.entity.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)          // 필드에만 붙이기
@Retention(RetentionPolicy.RUNTIME) // 런타임에 리플렉션으로 읽어야 하니까 RUNTIME
public @interface EncryptedOrNotText {
}


  (B: 리스터 구현)

import com.vwsai.project2.entity.annotation.EncryptedOrNotText;
// import com.vwsai.project2.entity.annotation.EncryptedText;  // 이건 더 안 쓸 거면 삭제

@Component
@Slf4j
public class EncryptionMongoEventListener extends AbstractMongoEventListener<Object> {

    private final IpoCreditMasterService cryptoService;

    public EncryptionMongoEventListener(IpoCreditMasterService cryptoService) {
        this.cryptoService = cryptoService;
    }

    @Override
    public void onBeforeConvert(BeforeConvertEvent<Object> event) {
        processFields(event.getSource(), true);
    }

    @Override
    public void onAfterConvert(AfterConvertEvent<Object> event) {
        processFields(event.getSource(), false);
    }

    private void processFields(Object entity, boolean encrypt) {
        if (entity == null) return;

        Class<?> current = entity.getClass();
        while (current != null && current != Object.class) {
            for (Field field : current.getDeclaredFields()) {

                // 🔽 여기 어노테이션 타입만 바꿔주면 됨
                if (!field.isAnnotationPresent(EncryptedOrNotText.class)) {
                    continue;
                }
                if (field.getType() != String.class) {
                    continue;
                }

                field.setAccessible(true);
                try {
                    String value = (String) field.get(entity);
                    if (value == null || value.isEmpty()) {
                        continue;
                    }

                    String processed;
                    if (encrypt) {
                        processed = cryptoService.encrypt(value);
                    } else {
                        try {
                            processed = cryptoService.decrypt(value);
                        } catch (Exception e) {
                            log.error("복호화 실패: field={}, value={}", field.getName(), value);
                            processed = value; // 섞인 데이터 방어
                        }
                    }
                    field.set(entity, processed);
                } catch (Exception e) {
                    log.error("암/복호화 처리 실패: field={}", field.getName(), e);
                }
            }
            current = current.getSuperclass();
        }
    }
}

 

Posted by yongary
,

public String decryptOrPlain(AWSKMS kms, String input) {

    // 1) base64 decode 먼저 시도 (빠르고 로컬이므로 비용 없음)
    byte[] decoded;
    try {
        decoded = Base64.getDecoder().decode(input);
    } catch (IllegalArgumentException e) {
        // Base64가 아님 → 평문
        return input;
    }

    // 2) KMS 복호화 시도
    try {
        DecryptRequest request = new DecryptRequest()
                .withCiphertextBlob(ByteBuffer.wrap(decoded));
        ByteBuffer plainBytes = kms.decrypt(request).getPlaintext();
        return StandardCharsets.UTF_8.decode(plainBytes).toString();

    } catch (cohttp://m.amazonaws.services.kms.model.InvalidCiphertextException e) {
        // "이건 암호문이 아니다" 라고 AWS가 확정해주는 예외
        return input;

    } catch (Exception e) {
        // 나머지는 실제 장애 → 평문으로 오인하면 절대 안 됨
        throw new RuntimeException("KMS decrypt failed", e);
    }
}

Posted by yongary
,

현재 log_statment 조회:
psql에서(아무 db에서) 
> SHOW log_statement;
==> 보통은 이것만 세팅하면 됨. 
 (이거 세팅 하면 log_min_duration_statement값에 상관없이 다 찍힘)


🔹 pgaudit 없이 “누가 어떤 query 실행했는지” 남기는 log_statement 최소 세팅

핵심은 두 가지야:

  1. 쿼리를 로그로 남기고(log_statement / log_min_duration_statement)
  2. 로그에 유저명을 찍게 만드는 것(log_line_prefix)

1) RDS 파라미터 그룹에서 바꿀 것

RDS > Parameter groups에서 해당 Postgres 파라미터 그룹 편집해서 대략 이렇게:

 
-- 필수
log_statement = 'none'          -- 또는 'mod' / 'ddl' / 'all' 중 선택
log_min_duration_statement = 0  -- 또는 N ms (느린 쿼리만)

-- 유저/DB 정보 찍기 위해
log_line_prefix = '%t [%p] %u %d %h '
  • %u : 유저명 (누가)
  • %d : DB명
  • %h : 클라이언트 호스트(IP)
  • %t : 시간
  • %p : 프로세스 ID

“정말 다 보고 싶다”면

log_statement = 'all'
log_min_duration_statement = -1
 

모든 쿼리가 “유저 + 쿼리 내용” 형태로 CloudWatch/로그에 다 찍힘.
(트래픽 많으면 로그 엄청 늘어나는 건 주의!)

“쓰기류만 보고 싶다”면 (실무에서 많이 씀)

 
log_statement = 'mod'           -- INSERT/UPDATE/DELETE만
log_min_duration_statement = -1

→ SELECT는 안 찍고, 데이터 변경 쿼리만 유저와 함께 기록.


🔹 그럼 로그는 어떻게 생기냐?

설정이 잘 되면 RDS 로그/CloudWatch에 이런 식으로 찍혀:

 
2025-12-04 10:23:45 [12345] appuser mydb 10.0.0.12 LOG:  statement: SELECT * FROM orders WHERE id = 1;
2025-12-04 10:24:01 [12346] admin   mydb 10.0.0.13 LOG:  statement: UPDATE users SET ...;

이 한 줄만으로:

  • 누가(appuser/admin)
  • 어느 DB에서(mydb)
  • 어디서(10.0.0.12)
  • 어떤 SQL을(SELECT/UPDATE …)

까지 다 파악 가능.


🔹 pgaudit vs 기본 로그 비교

항목                                기본 로그 (log_statement 등)                          pgaudit
설정 난이도 비교적 간단 조금 더 복잡 (확장 설치/설정)
볼 수 있는 내용 쿼리 텍스트 + 유저 쿼리 + 권한/객체 단위 Audit 정보
용도 “누가 어떤 SQL 날렸냐” 수준 컴플라이언스/보안 감사용(정교한 audit)

지금 말한 것처럼

“누가 어떤 query를 날렸는지만”
알고 싶은 거라면 기본 로그 설정만으로 충분하고, 훨씬 간단해.


🔚 요약

  • 정말 간단하게 하고 싶다 → pgaudit 말고 log_statement + log_line_prefix로 해결
  • 최소 세팅 예:
     
  • RDS 파라미터 그룹 변경 후, 인스턴스 Reboot 한 번 해주면 적용됨.
log_line_prefix = '%t [%p] %u %d %h '
log_statement = 'all'  -- or 'mod'
log_min_duration_statement = -1

원하면

  • “운영 서비스에 최소 영향만 주는 추천값”
  • “CloudWatch에서 해당 유저만 필터링해서 보는 법” 은 추가 필요.

    [변경방법]
    1. 데이터베이스 선택 후 아래->구성 탭에서 확인하고

    2. 데이터베이스 수정을 눌러 아래 그림에서 파라미터 변경하면 됨.

    3. 그 후에 rds instance REBOOT: (1~2분 서비스 중단) 이 필요함.

Posted by yongary
,

centos7에 설치/운영되고 있는 mongoDB 5를 mongoDB 7로 업그레이드 하기 위해서는
2번의 업그레이가 필요. 
(mongoDB 7.0.26 으로 설치해도 7.0.26-1 이 설치됨: 이버전으로 업그레이드 목표)


0. 공통 사전 준비

버전/구성 파악
FCV가 "version" : "5.0" 인지 확인 (5.0.x에서 6.0으로 올릴 때 전제조건)MongoDB


mongosh --eval 'db.version()' mongosh --eval 'rs.status()' mongosh --eval 'db.adminCommand({getParameter:1, featureCompatibilityVersion:1})'


백업 필수

각 노드에서 mongodump 로 전체 백업 (tools는 CentOS 7 지원)MongoDB

mongodump --host <primary-host>:27017 --authenticationDatabase admin \ -u <user> -p<password> --out /backup/mongo_$(date +%F)


드라이버/애플리케이션 호환성 확인
사용하는 드라이버가 MongoDB 6.0, 7.0을 지원하는지 체크 (공식 드라이버 호환 테이블 참고)MongoDB
MongoDB 공식 yum repo 확인
현재 /etc/yum.repos.d/mongodb-org-5.0.repo (혹은 비슷한 이름) 있을 것.
6.0 / 7.0 업그레이드 시 이 repo 파일을 버전별로 교체하거나, baseurl의 5.0 부분을 6.0 / 7.0 으로 바꾸는 식으로 진행.
예시(6.0용 repo, CentOS 7):

[mongodb-org-6.0] name=MongoDB Repository baseurl=https://repo.mongodb.org/yum/redhat/7/mongodb-org/6.0/x86_64/ gpgcheck=1 enabled=1 gpgkey=https://www.mongodb.org/static/pgp/server-6.0.asc

 

1단계: 5 -> 6

1단계: MongoDB 5.0.23 → 6.0.x (Replica Set 기준)

MongoDB 공식 문서에서도 5.0 → 6.0 업그레이드는 replica set의 각 멤버를 5.0에서 6.0으로 순차 업그레이드하는 방식으로 안내합니다.MongoDB+1

1-1. 세컨더리 업그레이드 (각각 순서대로)

각 세컨더리에서 아래를 한 대씩 수행합니다.

  1. 해당 노드 상태 확인
    mongosh --host <secondary-host> --eval 'rs.isMaster()' # "secondary" 인지 확인
  2. 서비스 중지
    sudo systemctl stop mongod

  3. yum repo를 6.0용으로 변경
  4. $sudo yum clean all
    $sudo yum makecache
    이게 안되면..
    (1.centOs 를 기본으로 바꾸고)
    sudo sed -i 's|mirrorlist=|#mirrorlist=|g' /etc/yum.repos.d/CentOS-*.repo
    sudo sed -i 's|#baseurl=http://mirror.centos.org/centos/$releasever|baseurl=http://vault.centos.org/7.9.2009|g' /etc/yum.repos.d/CentOS-*.repo


    (2.ius 업데이트 차단)
    sudo sed -i 's/enabled *= *1/enabled=0/g' /etc/yum.repos.d/ius*.repo  후에

    sudo yum --disablerepo="*" --enablerepo="mongodb-org-6.0" makecache

    MongoDB 6.0으로 패키지 업그레이드

    $ sudo yum update -y mongodb-org-6.0.24 mongodb-org-server-6.0.24 mongodb-org-shell-6.0.24 mongodb-org-mongos-6.0.24 mongodb-org-tools-6.0.24

    안되면 

    //$sudo yum install -y mongodb-org
    # 또는
    //$ sudo yum update -y mongodb-org mongodb-org-server mongodb-org-shell mongodb-org-mongos mongodb-org-tools

    (혹시 gpg key= 패키지 키 설치에러면)
    $ sudo rpm --import https://pgp.mongodb.com/server-6.0.asc
    후에
    $ sudo rpm -qi gpg-pubkey-64c3c388-* 로 확인

    sudo yum --disablerepo="*" --enablerepo="mongodb-org-6.0" makecache 

    ==> 있으면 다시 yum update 
    sudo yum --disablerepo="*" --enablerepo="mongodb-org-6.0" update -y mongodb-org-6.0.24 mongodb-org-server-6.0.24 mongodb-org-shell-6.0.24 mongodb-org-mongos-6.0.24 mongodb-org-tools-6.0.24

  5.  서비스 재시작 & 상태 확인
    (mongod --version 으로 확인해보고)

    sudo systemctl start mongod sudo systemctl status mongod
  6. Replica Set에서 정상 합류 확인
     
    mongosh --host <primary-host> --eval 'rs.status()'
    • 해당 노드가 SECONDARY 상태로 돌아오면 다음 세컨더리로 넘어갑니다.
    • 세컨더리 하나 업그레이드가 완전히 끝난 뒤 다음 세컨더리 진행 (동시에 하지 말 것).
  7. Primary에서:
    mongosh --host <primary-host> --eval 'rs.status()'
    해당 노드가 SECONDARY 상태로 돌아오면 다음 세컨더리로 넘어갑니다.

    세컨더리 하나 업그레이드가 완전히 끝난 뒤 다음 세컨더리 진행 (동시에 하지 말 것).
sudo sed -i 's|mirrorlist=|#mirrorlist=|g' /etc/yum.repos.d/CentOS-*.repo
sudo sed -i 's|#baseurl=http://mirror.centos.org/centos/$releasever|baseurl=http://vault.centos.org/7.9.2009|g' /etc/yum.repos.d/CentOS-*.repo

1-2. Primary 업그레이드

  1. Primary를 강제로 Step Down
    • 다른 노드가 자동으로 Primary가 됩니다.
       
      mongosh --host <primary-host> rs.stepDown(60) // 60초 동안 재선출 허용
  2. 원래 Primary 노드에서 mongod 중지
    sudo systemctl stop mongod
  3. 해당 노드도 repo를 6.0용으로 변경 & 패키지 업그레이드
     
    sudo yum clean all sudo yum makecache sudo yum install -y mongodb-org
  4. 서비스 시작
    sudo systemctl start mongod
  5. Replica Set 전체 버전 확인
     
    Primary(새로 선출된 노드)에서:
mongosh
rs.status()
db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })
  • 모든 멤버의 mongod --version이 6.0.x 인지 확인.


1-3. FCV를 6.0으로 올리기

MongoDB는 메이저 업그레이드 시 바이너리 버전 → FCV 순서로 올리라고 합니다.MongoDB+1

  1. Primary에서 실행:
    db.adminCommand({ setFeatureCompatibilityVersion: "6.0" })
  2. 다시 확인:
    db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 }) # "version" : "6.0" 이어야 함
  3. 애플리케이션 기능 테스트, 로그 에러 여부 확인.

 

 

2단계: MongoDB 6.0.x → 7.0.x 업그레이드

MongoDB 7.0 업그레이드 문서도 동일한 패턴(세컨더리 → Primary, FCV 설정)을 안내합니다.MongoDB+1

2-1. 사전 확인

  1. 7.0 호환성 변경 사항 검토
    • 인덱스, 제거된 옵션, 암호화 컬렉션, FCV 다운그레이드 제한 등.MongoDB+1
  2. 드라이버가 7.0 지원하는지 재확인
  3. FCV가 현재 6.0인지 확인
    db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })

2-2. 세컨더리부터 7.0으로 업그레이드

각 세컨더리에서 다시 한 대씩 아래 수행:

  1. 상태 확인 (SECONDARY인지)
  2. mongod 중지
    sudo systemctl stop mongod
  3. yum repo를 7.0용으로 변경
     
    [mongodb-org-7.0]
    name=MongoDB Repository
    baseurl=https://repo.mongodb.org/yum/redhat/7/mongodb-org/7.0/x86_64/
    gpgcheck=1
    enabled=1
    gpgkey=https://www.mongodb.org/static/pgp/server-7.0.asc
  4. 패키지 업그레이드
     
    sudo yum clean all sudo yum makecache sudo yum install -y mongodb-org
  5. 서비스 시작 & 상태 확인 (rs.status()로 SECONDARY 복귀 확인)


  6. secondary 한대 추가될때마다 application 에 IP 앞쪽에 추가/뒤쪽에 삭제 해서 배포
    (단, 다른글 - add/remove글 참조)

2-3. Primary 업그레이드

  1. Primary에서 step down
     
    mongosh --host <primary-host> rs.stepDown(60)
  2. 해당 노드에서 mongod 중지 → repo 7.0으로 변경 → 패키지 업그레이드 → mongod 시작
  3. 전체 상태 점검
    rs.status() # 각 노드의 mongo --version 도 7.0.x 인지 확인

2-4. FCV를 7.0으로 올리기 (주의)

MongoDB 7.0에서는 FCV를 올린 뒤에는 다운그레이드가 훨씬 까다롭다고 명시합니다.MongoDB+1

  1. 충분히 테스트한 뒤, Primary에서:
     
    db.adminCommand({ setFeatureCompatibilityVersion: "7.0" })
  2. 확인:
     
    db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 }) # "version" : "7.0"
  3. 모든 애플리케이션 기능/쿼리 테스트, 로그 모니터링.

3. CentOS 7에서의 현실적인 권장 사항

  • MongoDB는 RHEL/CentOS 7/8/9를 지원 리스트에 올리고 있지만MongoDB
  • CentOS 7 자체가 2024-06-30 EOL이라 보안 패치가 더 이상 안 나옵니다.
  • 운영 환경이면,
    • **새 서버(RHEL 8/9, Rocky 8/9, Alma 8/9, Amazon Linux 2023 등)**에 MongoDB 7.0 새로 설치
    • 기존 5.0.23 클러스터에서 mongodump → 새 클러스터로 mongorestore 또는
    • Replica Set을 새 서버로 확장(add member) 후, 구 서버 멤버를 하나씩 제거하는 방식으로 롤링 마이그레이션
      같은 OS 마이그레이션 + 버전 업그레이드 계획을 같이 가져가는 걸 개인적으로 가장 추천합니다.
Posted by yongary
,

1. rs.status() 로 state=2 (secondary), health=1  이면 sync가 완료된 것임.

2. 한대 추가 후, 삭제 하기

rs.add("NEW_IP:27017")

js
코드 복사
rs.status()
🔎 state: 2 = Secondary 가 될 때까지 기다리기 (sync 시간 필요)


rs.remove("OLD_IP:27017")


2-1: 중요 - application 배포 하기. (바뀐 IP 적용해서 배포하기)
위 스텝 2번 반복.

 

중요: 마지막 primary 변경전에는 primary를 다른 2번서버로 옮기기.

cfg = rs.conf()
cfg.members[0].priority = 0    // 기존 Primary
cfg.members[1].priority = 2    // 새로 Primary가 되길 원하는 노드
rs.reconfig(cfg)



3. primary는 secondary처럼  conf change하고시기고
     application 배포 하기. (바뀐 IP 적용해서 배포하기)


그 후 primary 변경 후
마지막 application 배포 하기.

Posted by yongary
,

부제: mongo-keyfile 을 설정해서 mongoDB를 restart해도 auth를 활성화 하지 않은 상태로 유지하기


0. 아래 1번부터 하면 좋지만, keyfile이 없는 서버와 있는서버간 통신이 안되는 문제가 있으므로
- security:
  keyFile: /etc/mongo/mongo-keyfile
  transitionToAuth: true
  authorization: disabled

로 설정해서 3,2,1 순서로 restart하고

다되면 , mongoDB user추가 및 application 소스에 반영한 후에

# transitionToAuth: true 로 코멘트 하고, 3,2,1 다시 restart


1. keyFile = “노드끼리 비밀 합의용 패스워드”

keyFile은 원래 replica set / sharded cluster 노드끼리 서로 인증하는 용도입니다.

  • security.keyFile: /etc/mongo-keyfile 만 설정하고
  • security.authorization: disabled 로 두면:

👉 결과

  • 서버끼리(replica set 멤버 간):
    • keyFile을 사용해서 internal authentication을 합니다.
  • 클라이언트(SPRING, mongo shell 등):
    • 여전히 user/password 없이 접속 가능
    • 기존처럼 mongosh --host ... 하면 그냥 접속

즉, keyFile만 설정했다고 해서 바로 “유저 인증 필수” 모드로 바뀌진 않습니다.
유저/비밀번호 강제를 하려면 authorization: enabled 가 결정적 스위치입니다.


2. 그래서 rollout 순서는 이렇게 가면 돼요

당신이 계획한 것처럼:

  1. 지금:
    • authorization: disabled
    • keyFile 없음
    • 아무나 접속 가능
  2. 1단계 – keyFile만 먼저 적용 (auth 여전히 꺼둔 상태)
    • 모든 노드에 /etc/mongo-keyfile 배포
    • mongod.conf에:
    •  
      security: authorization: disabled keyFile: /etc/mongo-keyfile
    • replica set이면 롤링 재시작
    • 이때도 여전히 애플리케이션은 비번 없이 접속 가능
  3. 2단계 – 유저 생성 + 스프링부트에서 계정/비번 사용하도록 변경
    • 아직 authorization: disabled라
      • 유저를 만들어도
      • 비번을 보내도 / 안 보내도
        전부 통과됩니다.
    • 이 상태에서 Spring Boot 설정을 미리:
    •  
      spring.data.mongodb.uri=mongodb://appUser:강력비번@host1,host2,host3/yourDb?replicaSet=rs0&authSource=admin
  4. 3단계 – 마지막에 auth ON
     
    security: authorization: enabled keyFile: /etc/mongo-keyfile
    로 바꾸고, 다시 replica set 롤링 재시작.
    • 이 시점부터는
      • 계정/비번 없이 접속하는 옛날 클라이언트 → 전부 접속 실패
      • 이미 계정/비번 쓰도록 바꾼 Spring Boot → 정상 동작
  5. 이제 정말로:

3. 한 줄로 요약

keyFile 설정 + 재시작만으로는 클라이언트 인증이 강제되지 않습니다.

그래서 원하는 대로
“keyFile 먼저 → 나중에 authorization: enabled”
순서로 점진적 전환(거의 무중단) 진행이 가능합니다.

 

Posted by yongary
,

🎯 요약

단계내용
1 (sudo -iu root 후에)
openssl rand -base64 756 > mongo-keyfile
2 chmod 600 & chown mongod
3 모든 노드 /etc/mongo-keyfile로 SCP
4 mongod.conf에 authorization + keyFile 설정
5 replica set 롤링 재시작
6 rs.status() 확인

3서버에 완전히 동일한 mongo-keyfile이 복사되어야 함

상세 설명:
MongoDB Replica Set에서 auth를 켜려면 노드 간 인증용 keyFile을 동일하게 만들어서 모든 서버에 배포해야 합니다.
AWS EC2(리눅스) 기준으로 MongoDB 5.0.23에서 사용할 수 있는 안전한 방법을 설명할게요.


🔐 Mongo KeyFile 생성 & 배포 절차 (EC2 / Linux)

📌 1) EC2 한 대에서 keyFile 생성

아래 명령을 실행 (지금 로그인한 서버 아무 곳이나 상관없음):

 
openssl rand -base64 756 > mongo-keyfile
  • 파일 길이는 at least 32 bytes 필요하지만
    MongoDB 권장값인 756 bytes 사용 (5.x에서도 OK)
  • 파일 이름은 아무거나 가능 (mongo-keyfile, rs.key, 등)

📌 2) 권한 설정 (반드시 600)

MongoDB는 keyFile 권한이 600 아니면 실행 실패합니다.

 
chmod 600 mongo-keyfile

(소유자는 MongoDB 사용자면 더 안전함)

 
chown mongod:mongod mongo-keyfile # 시스템마다 mongod 또는 mongodb

만약 MongoDB 프로세스가 mongod 유저가 아닌 root로 실행되고 있다면
chown 하지 않아도 됨. (권장 X → 되도록 mongod 유저로)


📌 3) keyFile을 Replica Set 모든 서버에 복사

각 EC2 인스턴스에 SSH로 복사:

 
scp mongo-keyfile ec2-user@<SERVER1_IP>:/etc/ scp mongo-keyfile ec2-user@<SERVER2_IP>:/etc/ scp mongo-keyfile ec2-user@<SERVER3_IP>:/etc/

그리고 각 서버에서 다시 권한 재확인:

 
chmod 600 /etc/mongo-keyfile chown mongod:mongod /etc/mongo-keyfile

3개 노드가 완전히 동일한 keyFile을 사용해야 합니다.
절대 각각 생성하면 안 됩니다.


📌 4) mongod.conf 수정 (각 서버 공통)

/etc/mongod.conf 파일 편집:

 
security: authorization: enabled keyFile: /etc/mongo-keyfile

또는 기존에 security: 블록이 있다면 아래처럼 통합:

 
security: authorization: enabled keyFile: /etc/mongo-keyfile

keyFile 설정 + authorization을 같이 써야 합니다.
keyFile만 넣고 auth 안 넣으면 replica set 인증만 되고 클라이언트는 무조건 실패합니다.


📌 5) 노드별 롤링 재시작

각 서버에서 한 대씩 순서대로:

 
sudo systemctl restart mongod

🚨 주의

Primary를 마지막에 재시작해야 합니다.
Secondary 2대 → Primary 순서.

Primary 먼저 끄면 다운타임 발생.


📌 6) 상태 확인

Primary에 접속한 후:

 
rs.status()

정상 replica set이면 OK.


🧠 추가 Tip (AWS EC2 운영 시 추천)

✔ KeyFile 보안을 위해 권장

 
chmod 400 /etc/mongo-keyfile

MongoDB는 600 또는 400 모두 허용합니다.

✔ EC2 UserData로 자동 배포하는 경우

  • Cloud-init에서 keyFile copy 후 chmod + chown 반드시 적용

✔ KeyFile Backup

  • keyFile은 분실하면 replica set이 절대로 부활 못합니다.
  • 안전한 S3/KMS 또는 Parameter Store에 백업 권장
Posted by yongary
,

1. 개념부터 정리

MongoDB는 크게 두 상태 중 하나입니다.

  1. authorization 꺼짐 (–-auth 없음, security.authorization: disabled)
    • 비번 여부 상관 없이 모든 연결이 풀 권한
    • 클라이언트가 username/password를 보내도 그냥 무시하고 동작
  2. authorization 켜짐 (–-auth, security.authorization: enabled)
    • 모든 요청은 인증 필수
    • 유저/롤 기반 권한 체크 수행

즉, “비번 있어도 되고 없어도 되고” 라는 중간 모드는 없어요.
하지만 auth 끄고 있는 동안에도 미리 계정을 만들어 놓고,
앱 쪽은 비번을 먼저 사용하게 바꾸는 건 가능
합니다.


2. 전체 전략 요약 (거의 무중단 버전)

  1. 현재: auth OFF 상태
  2. 관리용/앱용 사용자 미리 생성 (auth OFF 상태에서)
  3. Spring Boot 설정을 수정해서 username/password로 접속하도록 배포
  4. MongoDB 설정에 auth 켜고, replica set이면 노드별 롤링 재시작

이 순서로 하면:

  • 앱 입장:
    • 1~3단계: 비번 없어도 잘 되고, 비번 보내도 잘 됨 (auth OFF라 그냥 통과)
    • 4단계: auth ON 이후에도 이미 비번을 보내고 있으므로 정상 동작
  • 기존 “비번 안 쓰는 옛날 설정” 앱은
    • 4단계 이후부터는 접속 실패 → 이 때 이미 전부 새 설정으로 바꿔 둔 상태여야 함

3. 단계별로 조금 더 구체적으로

✅ 1단계: 관리자/앱 계정 만들기 (auth OFF 상태에서)

mongosh(또는 mongo)로 접속:

 
mongosh --host <primary-host>:27017

admin DB에서 계정 생성:

 
use admin; // 관리용 계정 (DBA용) db.createUser({ user: "adminUser", pwd: "강력한_비번", roles: [ { role: "userAdminAnyDatabase", db: "admin" }, { role: "dbAdminAnyDatabase", db: "admin" }, { role: "readWriteAnyDatabase", db: "admin" }, ] }); // 애플리케이션용 계정 (권한 최소화) db.createUser({ user: "appUser", pwd: "또_다른_강력한_비번", roles: [ { role: "readWrite", db: "yourAppDb" } // 실제 사용하는 DB명 ] });

주의: 지금은 auth OFF라 사실상 아무 제약 없이 createUser가 되지만,
나중에 auth ON 하면 이 계정들이 진짜로 쓰이게 됩니다.


✅ 2단계: Spring Boot 쪽에서 먼저 비번 사용하도록 변경

application.yml 예시 (mongoTemplate 쓰는 경우도 똑같이 설정):

 
spring: data: mongodb: uri: mongodb://appUser:또_다른_강력한_비번@host1:27017,host2:27017,host3:27017/yourAppDb?replicaSet=rs0&authSource=admin

혹은 application.properties:

 
spring.data.mongodb.uri=mongodb://appUser:또_다른_강력한_비번@host1:27017,host2:27017,host3:27017/yourAppDb?replicaSet=rs0&authSource=admin

그리고 이 설정으로 애플리케이션 배포/롤링 배포를 먼저 합니다.

  • 이 시점에는 Mongo가 auth OFF →
    비번이 틀려도 연결은 그냥 성공합니다.
  • 하지만 나중에 auth ON 하면 이 계정 정보가 실제로 사용되죠.

✅ 3단계: MongoDB에 auth 활성화 (security.authorization: enabled)

3-1. mongod.conf 수정

각 노드의 /etc/mongod.conf (또는 사용 중인 conf)에서:

 
security: authorization: enabled # replica set이면 keyFile도 같이 설정해야 함 (이미 쓰고 있을 수도 있음) # keyFile: /etc/mongo-keyfile

replica set인데 아직 keyFile 안 쓰고 있다면,
별도로 keyfile 생성해서 세 노드에 같은 파일/권한으로 배포해야 합니다.
(이건 내부 노드 간 인증용)

3-2. replica set이라면 롤링 재시작

예: 노드 3대 (primary 1 + secondary 2)라고 가정.

  1. secondary 1
    • mongod stop
    • conf 수정 (authorization: enabled, keyFile 설정)
    • mongod start
    • rs.status()로 정상 동기 확인
  2. secondary 2
    • 위와 동일하게
  3. primary
    • primary를 마지막에 재시작 → 이때 잠깐 primary 선거가 일어날 수 있지만
      Driver가 잘 잡아주면 애플리케이션은 거의 문제없이 넘어감

이 과정을 통해 전체 replica set이 auth ON 상태로 전환됩니다.


4. 진짜 “무중단”이 가능한가?

  • 싱글 인스턴스 MongoDB라면:
    • auth 켜려면 어쨌든 한 번은 mongod 재시작이 필요 → 짧은 다운은 피하기 어려움
  • 3대 replica set이라면:
    • 각 노드를 하나씩 재시작하는 롤링 방식으로
      사용자 입장에서는 거의 무중단에 가깝게 전환 가능
    • 순간적인 primary 선거 동안 짧은 hiccup 정도는 있을 수 있음
    • spring-data-mongodb와 드라이버가 replica set + 재시도를 잘 처리해주면 체감 거의 없음

5. 질문하신 포인트에 대한 답 정리

비번이 없어도 동작하고 비번이 있어도 동작하도록 했다가,
나중에 비번 없이는 안 돌게 바꾸는 게 되나요?

  • Yes, 이 순서로 가능합니다:
    1. auth OFF 상태에서 사용자 계정들 미리 생성
    2. Spring Boot를 먼저 username/password 쓰게 배포
      → 이때는 “비번 있어도/없어도” 모두 동작하는 상태 (auth OFF니까)
    3. MongoDB에 authorization: enabled 설정 후 롤링 재시작
      → 이제는 비번 없이는 안 되는 상태로 전환
Posted by yongary
,