[AWS] EC2 접속용 SSH 키페어 분실 또는 손상 시 키 재생성 방법

현재 운영 중인 EC2 인스턴스를 SSH로 접속하는 용도로 SSH Key-pair를 사용합니다. 서버에 public-key를 저장해놓고 private-key는 로컬에 보관하여 사용합니다. 일반적으로 PEM 키를 사용합니다.

사용 중인 맥북을 공장초기화하면서 로컬에 있던 개인키(private-key)가 안전하게 i-Cloud에 저장되어있을 것이라 생각했습니다. 생각대로 공장초기화 후 i-Cloud를 통해 개인키를 다운받았고 EC2 인스턴스를 SSH로 접속해봤습니다. 그런데 접속이 되질 않습니다.

Permission denied (publickey).

개인키 퍼미션도 600으로 낮춰놨고 아무런 문제가 없었지만 계속 저 메세지만 출력되었습니다. 끝내 i-Cloud로 복원된 개인키가 손상되었다는 결론을 내게 되었고 키페어를 새로 생성하기로 했습니다.

키 페어 새로 생성하여 적용하는 방법

1. 우선 SSH를 접속할 로컬 환경에서 키를 새로 생성한다. 아래 명령을 입력한뒤 엔터 연타

$ ssh-keyhen -m PEM

2. 생성된 개인키의 공개키(public-key)를 출력하여 복사한다.

$ ssh-keygen -y -f {개인키 파일 경로}

3. AWS EC2 콘솔 -> 해당 인스턴스 중지 -> 인스턴스 우클릭 -> Instance Settings -> Edit user data -> 아래 스크립트 입력

Content-Type: multipart/mixed; boundary="//"
MIME-Version: 1.0

--//
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config.txt"

#cloud-config
cloud_final_modules:
- [users-groups, once]
users:
  - name: ec2-user
    ssh-authorized-keys: 
    - {위에서 복사한 공개키}

4. 저장 후 인스턴스 시작

5. SSH 접속 테스트

$ ssh -i {개인키 경로} ec2-user@{접속 호스트}

6. 잘 되면 성공. 이제 3번에서 저장했던 스크립트를 삭제해야한다. 저 스크립트는 EC2 서버에서 .ssh/authorized_keys 파일에 공개키를 넣어주는 역할이다. 즉, 한번 실행되고 나면 역할을 다하는데다 콘솔에 공개키를 포함한 스크립트가 존재하는 것은 보안상 리스크가 있기 때문에 삭제 해주는 것이 좋다. 인스턴스를 다시 중지한 후 3번 Edit user data 화면에서 저장한 스크립트를 삭제 -> 저장 후 인스턴스를 다시 시작한다.

7. 최종 접속 확인. 5번 접속 테스트를 다시 한번 해보고 접속이 잘된다면 키페이 교체는 완료된 것이다.

서울 땅사서 집짓기 #3편

3편은 자금 조달 방법에 대해 구체적으로 정리 해보려했으나 수시로 바뀌는 정책의 변화에 혹여 잘못된 정보가 전달 될 수 있지 않을까 우려했습니다. 그렇게 고민을 남겨둔채 2편 포스팅 후 1년이 넘어서야 3편을 쓰게 되었습니다.

이 내용은 당시 2018년도 기준으로 현재인 2021년과는 괴리가 있으니 숫자와 수치는 현재 기준과 맞지 않으니 참고바랍니다.

자금 조달의 가장 베스트는 뻔하지만 부모 찬스입니다. 물론 부모 찬스였다면 이 글을 쓸 필요도 이유도 없었을 겁니다. 건축 행위에 있어서 자금을 조달한다는 것은 뱃속에 아기에게 영양분을 쉬지 않고 공급해주는 과정과 흡사합니다. 그리고 아기가 태어나는 것을 준공에 비유할 수 있습니다. 계획하고 시작할 당시엔 자금 계획대로 흘러가지만 시간이 갈수록 추가 자금이 투입될 가능성이 커집니다. 때문에 공사는 가능한 짧은 기간에 끝내야 합니다. 하지만 무리하게 앞당길 경우 하자와 부실이 생기기 때문에 시공 견적 상 기간에 맞춰진다면 정말 베스트이고 통상 예상 공시 기간보다 1~2개월 변동이 생길 수 있습니다. 여기서 말하고자 하는 것은 결코 자금 계획은 계획일 뿐 최대한 보수적으로 버퍼를 염두해야 한다는 점입니다.

서론이 너무 길었습니다. 저의 경우 자금 조달은 정책 대출과 2금융권 대출, P2P대출을 조합하여 진행했습니다. 물론 저 많은 종류의 대출을 모두 일으켜 한꺼번에 집행한 것이 아니라 과정마다 취급할 수 있는 대출이 있어 성격에 맞게 갈아타기를 한 것입니다.

우선 건축을 위한 대지를 확보하기 위해 구옥 주택을 매입합니다. 이때 이용한 대출은 정책 대출 입니다. 정책 대출이란 주택 구입 시 정부에서 보증하는 대출 상품으로 디딤돌, 보금자리론 등이 이에 해당합니다. 당시 보금자리론을 이용했고 최초로 주택을 매입하게되면 금리 할인 혜택을 받을 수 있습니다. 아파트는 70%, 주택은 65%의 감정가 대비 한도가 가능했고 일반 시중은행의 주택담보대출보다 상대적으로 높은 한도로 받을 수 있었습니다. 다만 주택은 아파트처럼 KB시세와 같은 감정가 지표가 없기 때문에 대출 심사과정에서 감정을 진행합니다. 때문에 감정가와 대출 한도를 정확히 예측할 수 없는 부분이 있습니다.

이제 건축을 위해 구옥을 허물어야 합니다. 하지만 이때 문제가 있습니다. 직전에 받은 대출은 보금자리론이고 이 대출 상품은 주택담보대출의 성격을 띄고 있기 때문에 대지+건물이 함께 담보로 설정되어 있습니다. 즉, 건물을 허물 수 없습니다. 이를 인지하지 못하고 통보 없이 철거하게 되면 대출금의 일시반환 요구가 오게 되니 주의하셔야 합니다. 건물을 철거하기 전에 주택담보대출을 토지담보대출로 전환해야 합니다. 이때 순서는 구옥 건물에 대해 멸실 신청을 하여 접수 확인 서류를 받아 토지담보대출을 취급하는 금융기관에서 토지담보대출을 진행합니다. 이때 기존 주택담보대출을 실행 중인 금융기관과 함께 움직여야 합니다. 멸실 신청 일자에 건축물 멸실(철거)과 토지담보대출 실행, 주택담보대출금 상환, 주택담보 해제, 토지담보 설정이 동시에 이루어져야 합니다. 그래서 두 금융기관과 건축주의 협업이 이루어져야 합니다.

여기서 짚고 넘야가야 할 점은 무조건 대출이라고 내 자본 한 푼 없이 진행 할 순 없습니다. 일정의 자기자본금이 필요하며 이를 바탕으로 대출 심사가 이루어집니다. 자기자본금이란 금융권에서는 에쿼티라 불리며 전체 과정에 들어가는 비용에 내가 순수하게 투입할 수 있는 금액 비율을 말합니다. 통상 건축을 위한 대출을 일으키기 위해서는 최소 20%이상의 에쿼티를 요구합니다. 즉, 대출을 신청하고 20%의 에쿼티 금액을 증빙해야 합니다. 보통 통장 내역으로 증빙을 합니다.

이제 건물을 멸실(철거)하고 대지만 남은 상태입니다. 여기서 잠깐 체크해야 할 부분이 있습니다. 등기부등본 상 건물이 멸실처리 되었다하더라도 건축물대장에서 멸실 처리되지 않고 남아있는 경우가 있습니다. 건축물대장을 정리하지 않으면 서류상 하나의 대지에 건물이 2개가 존재하는 것이 되어버려 향후 준공을 위한 보존등기 신청 시 문제가 발생할 수 있습니다.

이상이 없다면 이제 건축자금대출(PF)를 일으켜 공사를 진행해야 합니다. PF대출은 프로젝트 파이낸싱의 줄임말이며 건물의 향후 가치를 예측하여 그 기준으로 대출금이 집행되는 구조입니다. 여기서 “향후 가치를 예측”이라는 부분 때문에 리스크를 안고가야 하는 부분이 있습니다. 예를 들어 건축 중 주변 악재로 가치가 하락하거나 사고로 공사가 중단되어 기간이 늘어나 자금 여력에 문제가 생기는 등 여러 변수를 안고 가야 합니다. 이 때문에 PF대출은 기본적으로 금리가 높고 취급을 하지 않으려는 곳도 많습니다. 그리고 가급적 공사를 하는 근처 은행을 찾아가는 것이 유리합니다. 주변 부동산의 시세나 가치는 주변 은행에서 제일 잘 알기 때문입니다.

건축자금대출(PF)은 오직 건축을 위해 투입되는 목적으로 사용되야하기 때문에 기성고에 따라 집행이 됩니다. 쉽게 설명하자면 10억의 PF대출이 승인되었다하더라도 한번에 10억이 실행되는 것이 아닌 시공 공정에 따라 기초공사 -> 골조공사 -> 마감공사 등 단계에 진입할때마다 쪼개어 일부가 집행됩니다.

제 경우 기성 은행에서 PF 대출을 받아주지 않아 P2P PF대출을 이용했습니다. 기성 은행에서 PF대출 승인이 되지 않았던 이유는 사업성이 떨어진다는 부분 때문이었습니다. 당시 저는 사업성이라는 단어를 듣고 “이건 사업이 아니고 제가 살려고 하는 겁니다.”라는 무지한 발언을 했었습니다. 여기서 사업성이란 간단히 말해 준공 후 대출 실행금을 온전히 상환할 수 있는지에 대한 지표입니다. 완공 후 분양이나 임대를 하지 않으면 부채는 그대로 떠안고 있어야 합니다. 그게 아니라면 타 금융기관에 대환을 통해 상환해야 합니다. 제 경우 임대 세대가 있으나 1세대 뿐이고 대부분의 면적을 분양이나 임대 없이 사용하는게 그들에게는 사업성을 저해하는 요인이 되었던 것이죠.

그래서 결국 선택한 것은 P2P에서 취급하는 PF대출이었습니다. 특성 상 대출이 실행되면 이자와 플랫폼 수수료를 선취하여 예치금에 키핑을 합니다. 키핑된 금액은 수수료와 매달 이자 상환시 자동으로 출금하여 이자를 납입합니다. 건축주 입장에서는 매달 이자 납부에 신경을 쓰지 않아 장점으로 작용할 수도 있지만 신청한 대출금의 수수료와 이자 만큼을 사용하지 못하므로 결국 그로 인해 비는 금액은 추가로 자금을 수혈해야 합니다. 설정된 대출 기간보다 완공일이 빠르다면 선취한 이자 차액은 돌려받습니다.

준공이 무사히 되었다면 건축자금대출(PF)을 상환해야 합니다. 보통 엑싯(Exit)한다는 표현을 사용합니다. 방법은 여러가지인데 대환, 임대, 분양, 매각 등이 있습니다. 다행히 대환으로 모든 상환이 된다면 문제가 없지만 그렇지 못할 경우 임대를 통한 임차보증금으로 상환 또는 분양이나 매각을 통한 상환을 해야 합니다. 수익을 목적으로 한다면 임대, 분양에 집중을 하겠지만 직접 주거 목적을 둔다면 금융기관에 주택담보대출로의 대환을 통해 해결합니다. 그래도 안되면 일부 공간을 임대하는 방법을 함께 사용합니다.

중공 후 대환, 임대, 분양등의 출구 전략이 중요합니다. 저 방법들로 해결이 되지 않는다면 건축자금대출(PF)의 연체로 이어지고 경매를 통한 매각이 이루어집니다. 이는 준공이 되지 않아 공사가 지연되는 상황에서도 마찬가지 입니다.

제 입장에서는 이번 3편에 소개된 이 과정들이 가장 힘들고 두려웠던 순간들이 많았습니다. 금융은 내가 원하는대로 설계를 할 수도 만들어낼 수도 없기 때문입니다. 자금이 끊기지 않도록 퇴직금까지 끌어다 마련하는등 가능한 모든 방법과 수단으로 순간순간 변칙적인 상황을 잘 헤쳐나갔던 것으로 기억합니다.

건축 과정은 다사다난 했지만 건물이 올라가는 것을 두 눈으로 보면서 에너지를 잃지 않고 준공때까지 포기하지 않고 도전했던 것 같습니다. 금융 조달의 과정은 매년 짧게는 매달 금융권의 조건과 상황이 다르게 적용되어 고정된 메뉴얼이 없습니다. 쉽지만은 않았던 과정이지만 항상 어딘가에 답은 있었기에 가능했고 답을 찾는 것은 본인의 몫이었습니다.

다음 편은 준공 직후 발생했던 이슈들과 대응했던 사례들을 정리해보겠습니다.

[React] AWS Amplify 환경 변수 추가 방법

React 프로덕트를 AWS Amplify를 이용해 배포 시 환경 변수를 통해 간편하게 개발과 운영 환경을 분기할 수 있습니다.

예를 들어 요청 API 도메인이 개발과 운영이 다른 경우 요청 도메인을 외부 변수로 빼서 관리를 해야합니다. 이를 기존에는 .env를 통해 처리했지만 Amplify에서는 환경 변수를 세팅할 수 있는 기능을 제공합니다.

AWS Amplify > 앱 설정 > 환경 변수 > REACT_APP_API_URL 변수명을 추가하고 해당하는 API URL을 넣어줍니다. 하나의 앱환경에 Git 브랜치 별로 구성되어있다면 대상 브랜치도 선택할 수 있습니다.

설정된 REACT_APP_API_URL 이라는 변수는 React 소스상에서 기존 .env 환경 변수 사용법과 동일하게 process.env.REACT_APP_API_URL로 설정 값을 사용할 수 있습니다.

단, 이미 배포된 상태에서 환경 변수를 추가하는 경우 적용되지 않고 재배포를 해야 추가/변경된 환경변수가 적용됩니다.

[Kotlin/Spring] JPA/QueryDSL KotlinDSL Gradle 설정 방법

Kotlin DSL 문법으로 QueryDSL을 Gradle로 설정하는 방법을 정리합니다.
아래 내용은 build.gradle.kts에서 JPA와 QueryDSL 관련 내용만 추려서 정리한 것입니다.

plugins {
    kotlin("plugin.jpa") version "1.4.10"
    kotlin("kapt") version "1.4.10"
}

dependencies {
  	implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("com.querydsl:querydsl-jpa")
    kapt("com.querydsl:querydsl-apt:4.2.2:jpa")
    annotationProcessor(group = "com.querydsl", name = "querydsl-apt", classifier = "jpa")
}

sourceSets["main"].withConvention(org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet::class){
    kotlin.srcDir("$buildDir/generated/source/kapt/main")
}

아래 링크에서 도움을 많이 받았습니다. 좀 더 상세한 정보가 있으니 참고해보시길 추천 합니다.
https://velog.io/@gosgjung/kotlin-DSL-gradle-QueryDSL-%EC%84%A4%EC%A0%95

[AWS] ElasticLoadBalancer(ELB) 적용된 ElasticBeansTalk(EB) Instance SSH 접속 방법

ElasticBeanstalk로 구성된 서비스에 https를 사용하려면 ELB를 구성하여 발급된 SSL인증서를 적용해야 합니다. 여기서 ELB는 인프라에 앞단에서 https(443 포트) 접근을 받아 ElasticBeanstalk(이하 EB)에 넘기는 역할을 합니다.

위 처럼 구성된 경우 EB 인스턴스의 SSH 접근이 EB public DNS를 통해 되지 않게 됩니다. 이 경우 EB에 해당하는 EC2 인스턴스의 Public IP 또는 Public DNS로 SSH 접속이 가능합니다. Public IP/Public DNS는 EC2 인스턴스 재시작 시 변경됩니다. IP가 변경되지 않도록 하려면 ElasticIP를 이용해 고정 IP를 할당받아야 합니다.

그 전에 EC2 Key-pair 생성(pem키 발급) 또는 등록이 필요하고 해당 EC2 인스턴스의 보안 그룹(SecurityGroup)에서 SSH 22번 포트 Inbound 설정이 필요합니다. 설정이 완료되었다면 아래와 같이 접근이 가능합니다.

$ ssh -i {개인키 경로} ec2-user@{EC2 PublicIP or PublicDNS}

[SpringBoot] Elastic Beanstalk Profile 변수 적용되지 않는 이슈

개인 프로젝트를 위해 Kotlin + Spring boot + AWS 환경에서 개발을 진행하고 있는데 삽질이 이만저만이 아니어서 계속 포스팅이 이어질 것 같습니다.

Elastic Beanstalk 줄여서 EB라 하겠습니다. EB에서 개발과 운영 환경 분기를 위해 환경변수를 세팅할 수 있습니다. 제 경우에는 application.properties를 이용해 application-dev.properties와 application-prod.properties 각 별도 파일로 분리시켰습니다. application.properties 정의를 하고 환경변수에 해당 환경에 대한 키워드를 대입하면 손쉽게(?) 환경 분기를 시킬 수 있습니다.

그런데 안되서 어제 하루 종일 삽을 펐습니다. 문제는 EB에서 SPRING_PROFILES_ACTIVE 변수를 dev로 지정했음에 불구하고 배포 시 이를 읽지 못하고 default profile이 적용되는 문제였습니다.

하루 반나절만에 해결이 되었습니다. 문제는 환경 변수 키워드가 원인이었고 SPRING_PROFILES_ACTIVE가 아닌 spring.profiles.active로 환경변수를 지정해줘야 했습니다.

맥북 터치바 사라진 ESC 키 살리는 방법

언제부턴가 사용중인 맥북의 터치바에 ESC 키가 나타나지 않고 있었다. OS 업데이트 이후 현상인가라고 생각했지만 쓰면 쓸수록 ESC 없는 키보드는 적응이 안됐다.

잠시 검색해보니 간단한 방법으로 살릴 수 있었다. 터치바 프로세스를 죽이면 죽인 프로세스가 다시 올라오면서 사라졌던 ESC 키가 돌아온다.

터미널을 실행 시키고 아래 명령을 입력 후 엔터, 맥북 계정의 비밀번호를 입력하면 된다.

$ sudo pkill TouchBarServer

[AWS] Amazon Linux AMI에서 PHP 7.x 설치

아마존 리눅스에서 PHP 7.x 버전 설치하는 방법을 소개합니다. EC2 인스턴스 생성 직후 $ sudo yum info php* 로 패키지를 조회 해보면 php 5.4 버전 기준의 패키지만 검색됩니다. 아래 단계를 진행하시면 php7 버전 설치가 가능합니다.

진행 방법

// php 최신 버전 조회
$ sudo amazon-linux-extras | grep php
// php 7.4 레포지토리 설치 (작성일 기준에서 최신버전은 php7.4)
$ sudo amazon-linux-extras install php7.4
// php 7.4 패키지 활성 (기존 설치된 버전이 있다면 disable 명령 후 진행)
$ sudo amazon-linux-extras enable php7.4
// php 모듈 설치
$ sudo yum install  php-cli php-common php-gd php-mbstring  php-mysqlnd php-pdo php-fpm php-xml php-opcache php-zip php-bcmath
// php 버전 확인
$ php -v

* 이 내용은 https://www.lesstif.com/lpt/amazon-linux-ami-php-7-3-77955353.html 을 바탕으로 재작성하였습니다.

[Chrome] input 자동 완성(autocomplete) 비활성 방법

크롬 브라우저에서 자동 완성을 비활성 시키는 방법을 정리합니다. 해결 방법은 의외로 간단했지만 검색해보면 각기 다른 해결 방법이 소개되어 있어 오히려 혼란스러웠던 것 같습니다. 다른 타입은 테스트해보지 않았지만 input type=”text” 에서는 적용되는 것을 확인했습니다.

<input type="text" name="userName" autocomplete="no" />

위 예제에서 중요한 부분은 autocomplete=”no” 입니다. 다른 구글링 내용들을 보면 autocomplete=”off”를 사용해보라는 언급들이 있지만 크롬에서는 동작하지 않고 자동 완성 목록을 뿌려줍니다. 약간의 발상 전환을 해서 제시되지 않은 키워드들을 넣어보았습니다. 결과는 성공이었습니다. 즉, off가 아닌 다른 키워드를 입력하면 됩니다. 일종의 핵같은 방법이라고 볼 수도 있습니다. 예제에서는 no라고 명시했습니다. 이 또한 크롬 브라우저 업데이트가 된다면 어떻게 될지는 모르니 주의가 필요합니다.

해당 테스트는 Chrome 81.0.4044.122(공식 빌드) (64비트) 버전에서 진행되었습니다.

jQuery Selector로 iframe 제어하는 방법

jQuery를 이용해 iframe 내에서 부모 document 그리고 부모 document 내에 존재하는 다른 iframe에 접근하는 방법을 우선 정리해보고 이를 응용할 수 있는 예시를 한 번 더 정리해보겠습니다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Code Sample</title>
  </head>
  <body>
    <div id="content">Content Sample</div>
    
    <iframe id="frameA" src="[frameA]"></iframe>
    <iframe id="frameB" src="[frameB]"></iframe>
  </body>
</html>

iframe 내에서 부모 document 접근 방법

<script>
  // 부모 도큐멘트에서 #content 엘리먼트를 참조하여 hide() 처리
  var parentDocumentContent = $("#content", parent.document);
  parentDocumentContent.hide();
</script>

iframe 내에서 부모 document의 다른 iframe 접근

<script>
  // 부모 도큐멘트의 #frameB iframe 내 #frameBContent 엘리먼트를 참조하여 hide() 처리
  var frameBContent = $("#frameBContent", parent.frames["frameB"].document);
  frameBContent.hide();
</script>

응용 : iframe 내 document의 height 값을 구해 iframe의 height 값을 세팅

<script>
// This iframe name is "contentFrame"

$(document).ready(function(){
  
  // 현재 document를 불러오는 iframe의 selector를 정의한다.
  var thisFrame = $("#contentFrame", parent.document);
  
  // 현재 document의 height 값을 정의한다.
  var documentHeight = $(document).height();

  // 부모 document에서 iframe을 thisFrame에 정의된 selector로 참조하여 iframe의 height값을 css로 설정한다.
  thisFrame.css("height", documentHeight + "px");
});
</script>

jQuery를 버리고 Vanilla JS로 옮겨타고 싶지만 제 자신이 너무 오랜 시간 jQuery에 익숙해진 듯 합니다.

그래서 다음 포스팅에서는 제가 주로 사용하는 jQuery 문법들을 정리하고 이에 대응하는 Vanilla JS 표현식을 알아보도록 하겠습니다.