<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Dev · 인재덕</title>
    <link>https://jared.lynskey.co.nz/ko/tags/dev/</link>
    <description>소프트웨어 엔지니어링과 개발 조직 리더십에 대한 이야기. 성과 리뷰, 채용과 팀 확장, CI/CD, 데브옵스 모니터링, 에이전틱 AI. 서울에서 씁니다.</description>
    <generator>Hugo</generator>
    <language>ko</language>
    <copyright>© 2026 인재덕</copyright>
    <lastBuildDate>Wed, 28 Oct 2026 00:00:00 +1300</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/ko/tags/dev/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>SSH 로그인 알림을 Slack으로 받기: PAM과 웹훅으로 설정하는 법</title>
      <link>https://jared.lynskey.co.nz/ko/posts/2021/2021-12-14-ssh-log/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ko/posts/2021/2021-12-14-ssh-log/</guid>
      <pubDate>Tue, 14 Dec 2021 11:30:03 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>dev</category>
      <category>full</category>
      <category>stack</category>
      <category>useful</category>
      <category>slack</category>
      <category>ssh</category>
      <category>proactive</category>
      <category>ops</category>
      <description>SSH 로그인 알림을 Slack으로 받는 방법입니다. PAM과 Slack Incoming Webhook을 이용해 누군가 서버에 SSH 접속을 시도하면 알림이 오게 하고, 로그인 성공 알림도 선택적으로 켤 수 있습니다.</description>
      <content:encoded><![CDATA[
<h2 class="relative group">목적
    <div id="목적" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%eb%aa%a9%ec%a0%81" aria-label="앵커">#</a>
    </span>
    
</h2>
<ul>
<li>SSH는 누군가 로그인을 시도할 때 원하는 명령을 실행하게 만들 수 있습니다. 이 글에서는 Slack과 연동하면서 PAM 실행 순서를 제어하는 방법을 소개합니다.</li>
</ul>

<h2 class="relative group">Slack API 웹훅
    <div id="slack-api-웹훅" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#slack-api-%ec%9b%b9%ed%9b%85" aria-label="앵커">#</a>
    </span>
    
</h2>
<ul>
<li>
<p>앱 만드는 방법은 <a href="https://api.slack.com/messaging/webhooks"  target="_blank" rel="noreferrer">여기</a>에 잘 정리되어 있습니다.</p>
</li>
<li>
<p>Slack 앱을 만들고 웹훅을 활성화한 뒤, SSH 알림을 받을 Incoming Webhook을 하나 만들어 두세요.</p>
</li>
</ul>

<h2 class="relative group">로그인 실패 알림 스크립트 만들기
    <div id="로그인-실패-알림-스크립트-만들기" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%eb%a1%9c%ea%b7%b8%ec%9d%b8-%ec%8b%a4%ed%8c%a8-%ec%95%8c%eb%a6%bc-%ec%8a%a4%ed%81%ac%eb%a6%bd%ed%8a%b8-%eb%a7%8c%eb%93%a4%ea%b8%b0" aria-label="앵커">#</a>
    </span>
    
</h2>
<ul>
<li>스크립트는 아무 데나 둬도 됩니다. 저는 <code>/var/opt/notify-attempt.sh</code>에 뒀습니다.</li>
</ul>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">#!/bin/bash
</span></span><span class="line"><span class="cl">if [ &#34;$PAM_TYPE&#34; != &#34;close_session&#34; ]; then
</span></span><span class="line"><span class="cl">        url=&#34;&lt;YOUR SLACK WEBHOOK&gt;&#34;
</span></span><span class="line"><span class="cl">        channel=&#34;#channel&#34;
</span></span><span class="line"><span class="cl">        host=&#34;$(hostname)&#34;
</span></span><span class="line"><span class="cl">        content=&#34;\&#34;attachments\&#34;: [ { \&#34;mrkdwn_in\&#34;: [\&#34;text\&#34;, \&#34;fallback\&#34;], \&#34;fallback\&#34;: \&#34;SSH login: $PAM_USER connected to \`$host\`\&#34;, \&#34;text\&#34;: \&#34;SSH login to \`$host\`\&#34;, \&#34;fields\&#34;: [ { \&#34;title\&#34;: \&#34;User\&#34;, \&#34;value\&#34;: \&#34;$PAM_USER\&#34;, \&#34;short\&#34;: true }, { \&#34;title\&#34;: \&#34;IP Address\&#34;, \&#34;value\&#34;: \&#34;$PAM_RHOST\&#34;, \&#34;short\&#34;: true } ], \&#34;color\&#34;: \&#34;#F35A00\&#34; } ]&#34;
</span></span><span class="line"><span class="cl">        curl -X POST --data-urlencode &#34;payload={\&#34;channel\&#34;: \&#34;$channel\&#34;, \&#34;mrkdwn\&#34;: true, \&#34;username\&#34;: \&#34;SSH Notifications\&#34;, $content, \&#34;icon_emoji\&#34;: \&#34;:inbox-tray:\&#34;}&#34; &#34;$url&#34; &amp;
</span></span><span class="line"><span class="cl">fi
</span></span><span class="line"><span class="cl">exit</span></span></code></pre></div></div>

<h3 class="relative group">다른 SSH 이벤트도 받고 싶다면
    <div id="다른-ssh-이벤트도-받고-싶다면" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%eb%8b%a4%eb%a5%b8-ssh-%ec%9d%b4%eb%b2%a4%ed%8a%b8%eb%8f%84-%eb%b0%9b%ea%b3%a0-%ec%8b%b6%eb%8b%a4%eb%a9%b4" aria-label="앵커">#</a>
    </span>
    
</h3>
<ul>
<li>로그인 성공 같은 다른 이벤트까지 알림을 받고 싶다면 파일을 두 개 만드는 걸 추천합니다.</li>
<li>각 파일에 Slack으로 보낼 JSON을 넣어두면 됩니다.</li>
<li><code>fallback\&quot;: \&quot;SSH login: $PAM_USER connected to \</code>$host`&quot;` 줄은 이벤트에 맞게 고쳐 주세요.</li>
<li>OpenSSH가 이 스크립트들을 어떻게 쓰는지는 아래 설정을 보면 알 수 있습니다.</li>
</ul>

<h3 class="relative group">ssh 설정
    <div id="ssh-설정" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ssh-%ec%84%a4%ec%a0%95" aria-label="앵커">#</a>
    </span>
    
</h3>
<p>성공 알림은 주석 처리해 뒀습니다. 쓰고 싶다면 주석을 풀면 성공할 때도 Slack으로 알림이 갑니다.
해당 줄: <code># auth  optional            pam_exec.so /var/opt/notify-login.sh</code></p>
<p>[success=2]는 로그인에 성공하면 두 줄을 건너뛴다는 뜻이라, 실패 알림이 실행되지 않고 넘어갑니다.</p>
<p>파일: <code>/etc/pam.d/common-auth</code></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># /etc/pam.d/common-auth - authentication settings common to all services
</span></span><span class="line"><span class="cl">#
</span></span><span class="line"><span class="cl"># This file is included from other service-specific PAM config files,
</span></span><span class="line"><span class="cl"># and should contain a list of the authentication modules that define
</span></span><span class="line"><span class="cl"># the central authentication scheme for use on the system
</span></span><span class="line"><span class="cl"># (e.g., /etc/shadow, LDAP, Kerberos, etc.). The default is to use the
</span></span><span class="line"><span class="cl"># traditional Unix authentication mechanisms.
</span></span><span class="line"><span class="cl">#
</span></span><span class="line"><span class="cl"># As of pam 1.0.1-6, this file is managed by pam-auth-update by default.
</span></span><span class="line"><span class="cl"># To take advantage of this, it is recommended that you configure any
</span></span><span class="line"><span class="cl"># local modules either before or after the default block, and use
</span></span><span class="line"><span class="cl"># pam-auth-update to manage selection of other modules. See
</span></span><span class="line"><span class="cl"># pam-auth-update(8) for details.
</span></span><span class="line"><span class="cl"># here are the per-package modules (the &#34;Primary&#34; block)
</span></span><span class="line"><span class="cl">auth  [success=2 default=ignore]   pam_unix.so nullok
</span></span><span class="line"><span class="cl">auth  optional            pam_exec.so /var/opt/notify-attempt.sh
</span></span><span class="line"><span class="cl"># here&#39;s the fallback if no module succeeds
</span></span><span class="line"><span class="cl">auth  requisite            pam_deny.so
</span></span><span class="line"><span class="cl"># prime the stack with a positive return value if there isn&#39;t one already;
</span></span><span class="line"><span class="cl"># this avoids us returning an error just because nothing sets a success code
</span></span><span class="line"><span class="cl"># since the modules above will each just jump around
</span></span><span class="line"><span class="cl"># For successful login, uncomment the below line, ensure the file exists.
</span></span><span class="line"><span class="cl"># auth  optional            pam_exec.so /var/opt/notify-login.sh
</span></span><span class="line"><span class="cl"># auth  required            pam_permit.so
</span></span><span class="line"><span class="cl"># and here are more per-package modules (the &#34;Additional&#34; block)
</span></span><span class="line"><span class="cl">auth  optional            pam_cap.so
</span></span><span class="line"><span class="cl"># end of pam-auth-update config</span></span></code></pre></div></div>

<h2 class="relative group">감사합니다!
    <div id="감사합니다" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%ea%b0%90%ec%82%ac%ed%95%a9%eb%8b%88%eb%8b%a4" aria-label="앵커">#</a>
    </span>
    
</h2>
<ul>
<li>궁금한 점이나 의견이 있으면 편하게 연락 주세요</li>
</ul>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2021/2021-12-14-ssh-log/featured.jpeg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
