<?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/"><channel><title>Selinux on Tarragon</title><link>https://tarrragon.github.io/blog/tags/selinux/</link><description>Recent content in Selinux on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 26 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/selinux/index.xml" rel="self" type="application/rss+xml"/><item><title>Mandatory Access Control（強制存取控制）：SELinux 與 AppArmor</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/mandatory-access-control/</link><pubDate>Mon, 26 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/mandatory-access-control/</guid><description>&lt;p>強制存取控制（mandatory access control，MAC）是作業系統核心依一份系統層級的政策決定程序能存取什麼的機制：政策寫出「哪一類程序可以對哪一類檔案、連接埠做什麼」，核心在每一次存取時比對，政策沒有准許的一律拒絕，檔案的擁有者與 root 都不能自己放寬。Linux 一般的檔案權限（擁有者、群組、&lt;code>rwx&lt;/code>）是自主存取控制（discretionary access control，DAC），由檔案的擁有者決定；兩層都通過，存取才成立。Linux 上常見的兩種實作是 SELinux（Oracle Linux、RHEL、Fedora 預設啟用）與 AppArmor（Ubuntu、Debian 預設啟用）。兩者的拒絕紀錄都要從 log 裡讀：SELinux 寫進 audit log，AppArmor 寫進核心訊息，核心訊息經由 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/journald/" data-link-title="journald（systemd-journald）" data-link-desc="用 journalctl 查不到重開機之前的紀錄、journal 佔掉多少磁碟不清楚、某個服務的 log 出現 Suppressed 訊息，或要判斷一筆紀錄該去 journal 還是 /var/log 底下的檔案找時">journald（systemd-journald）&lt;/a> 用 &lt;code>journalctl -k&lt;/code> 查得到。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>MAC 在一台對外服務的主機上負責的是程序被入侵之後的範圍：nginx 的程序被利用時，DAC 只看它以哪個使用者執行，MAC 另外把它限制在政策准許 web 伺服器碰的檔案與連接埠裡。代價是一類 DAC 看不出原因的拒絕：&lt;code>ls -l&lt;/code> 的權限都對、程式也找得到檔案，存取照樣失敗。長期上線的主機上，拒絕紀錄要定期讀（AppArmor 的那一份在 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/journald/" data-link-title="journald（systemd-journald）" data-link-desc="用 journalctl 查不到重開機之前的紀錄、journal 佔掉多少磁碟不清楚、某個服務的 log 出現 Suppressed 訊息，或要判斷一筆紀錄該去 journal 還是 /var/log 底下的檔案找時">journald（systemd-journald）&lt;/a> 收下的核心訊息裡），正常值是零，見 &lt;a href="https://tarrragon.github.io/blog/devops/operations/09-host-lifecycle/long-running-host-maintenance/" data-link-title="長期上線主機的維護項目：會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算" data-link-desc="單台 Linux 主機（VPS、雲端主機）長期上線時，沒有人操作也會改變的狀態與維護對它們做的事：log 與資料的寫入者和清理機制（logrotate、journald、容器 log）、磁碟寫滿時各元件的反應、每份 log 要讀的欄位、入口在主機與雲端各層的盤點（ss）、各條更新管道的安全更新與重開機（dnf-automatic、unattended-upgrades、needs-restarting）、會到期與會改變的設定、檢查間隔的推算，以及接手一台已上線的主機時先處理哪幾項">長期上線主機的維護項目：會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算&lt;/a>〈定期讀的 log：每一份要看的欄位與它代表的狀態〉；從備份還原之後要核對的標籤，見 &lt;a href="https://tarrragon.github.io/blog/devops/operations/09-host-lifecycle/suspend-and-resume-service/" data-link-title="服務暫停與恢復上線：只停服務、停止主機、終止主機到全部刪除各自保留的資源，暫停期間仍計費與仍被指向的資源，恢復前要查的漂移與恢復的順序" data-link-desc="一台主機上的服務要暫停一段時間再恢復：只停服務、停止主機、終止主機保留設定、全部刪除靠 repo 重建各自保留什麼與付出什麼，怎麼依保留的月費、重建的成本與漂移量選擇，暫停期間仍在計費的資源（含在作業系統裡 shutdown 不停止計費），仍被 DNS 與 CDN 指向的結果（Cloudflare 521、522，釋放 IP 之後的懸空 DNS），恢復前要重新確認的漂移，以及依元件依賴排出的恢復順序與驗證">服務暫停與恢復上線：只停服務、停止主機、終止主機到全部刪除各自保留的資源，暫停期間仍計費與仍被指向的資源，恢復前要查的漂移與恢復的順序&lt;/a>。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>服務回 403 或 permission denied，而 &lt;code>ls -l&lt;/code> 顯示的擁有者與權限允許這次存取。&lt;/li>
&lt;li>SELinux：&lt;code>getenforce&lt;/code> 印出 &lt;code>Enforcing&lt;/code>（拒絕並記錄）、&lt;code>Permissive&lt;/code>（只記錄不拒絕）或 &lt;code>Disabled&lt;/code>；拒絕紀錄寫進 audit log，&lt;code>ausearch -m AVC -ts recent&lt;/code> 列出最近的 &lt;code>avc: denied&lt;/code> 紀錄，每一筆寫出被拒絕的動作、程序的標籤（&lt;code>scontext&lt;/code>）與目標的標籤（&lt;code>tcontext&lt;/code>）。&lt;/li>
&lt;li>AppArmor：&lt;code>aa-status&lt;/code> 列出載入的設定檔與它們的模式；拒絕紀錄在核心 log 裡，&lt;code>journalctl -k | grep 'apparmor=&amp;quot;DENIED&amp;quot;'&lt;/code> 列得出來。&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>SELinux 的判斷依據是標籤：每個檔案、程序、連接埠都帶一個標籤，&lt;code>ls -Z&lt;/code> 與 &lt;code>ps -Z&lt;/code> 讀得到，政策寫的是標籤之間的規則（web 伺服器的程序 &lt;code>httpd_t&lt;/code> 可以讀 &lt;code>httpd_sys_content_t&lt;/code> 的檔案）。所以 SELinux 的拒絕多半是標籤不對，而標籤錯掉最常發生在檔案搬動的時候：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>mv&lt;/code> 保留原本的標籤&lt;/strong>：從家目錄 &lt;code>mv&lt;/code> 到 &lt;code>/var/www&lt;/code> 的檔案仍然帶著家目錄的標籤，nginx 讀不到；&lt;code>cp&lt;/code> 建的是新檔，取目的地目錄的預設標籤。&lt;/li>
&lt;li>&lt;strong>從備份還原&lt;/strong>：&lt;code>tar&lt;/code> 沒有帶 &lt;code>--selinux&lt;/code> 這類保留延伸屬性的選項、或備份存在不支援延伸屬性的位置時，還原出來的檔案不帶原本的標籤。&lt;/li>
&lt;li>&lt;strong>修法是依政策重設標籤，不是關掉 SELinux&lt;/strong>：&lt;code>restorecon -Rv &amp;lt;目錄&amp;gt;&lt;/code> 依政策把檔案的標籤改回那個路徑的預設；網站放在政策不認得的路徑時，先用 &lt;code>semanage fcontext&lt;/code> 登記那個路徑該有的標籤，再跑 &lt;code>restorecon&lt;/code>。程序要做政策預設不准的事（nginx 往外連到後端）時，改的是對應的布林開關（&lt;code>setsebool -P httpd_can_network_connect on&lt;/code>）。&lt;code>setenforce 0&lt;/code> 把整台主機切到 Permissive，拒絕全部消失，防護也一起消失，只適合用來確認問題是不是 SELinux 造成的。&lt;/li>
&lt;/ul>
&lt;p>AppArmor 的判斷依據是路徑：每個程式的設定檔寫出它能存取哪些路徑，不帶標籤，所以搬動檔案不會改變它的判斷；網站放到設定檔沒寫的路徑時，改的是那個程式的設定檔。&lt;/p>
&lt;p>這張卡的指令與行為取自 SELinux 與 AppArmor 的官方文件與 RHEL 的 SELinux 指南，沒有在本站的主機上逐一實跑。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/journald/" data-link-title="journald（systemd-journald）" data-link-desc="用 journalctl 查不到重開機之前的紀錄、journal 佔掉多少磁碟不清楚、某個服務的 log 出現 Suppressed 訊息，或要判斷一筆紀錄該去 journal 還是 /var/log 底下的檔案找時">journald（systemd-journald）&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/docker-named-volume-ownership/" data-link-title="Docker Named Volume Ownership（掛載點擁有者）" data-link-desc="container 內非 root 使用者寫不進掛載的 named volume、出現 permission denied 時回來讀">Docker Named Volume Ownership（掛載點擁有者）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>強制存取控制（mandatory access control，MAC）是作業系統核心依一份系統層級的政策決定程序能存取什麼的機制：政策寫出「哪一類程序可以對哪一類檔案、連接埠做什麼」，核心在每一次存取時比對，政策沒有准許的一律拒絕，檔案的擁有者與 root 都不能自己放寬。Linux 一般的檔案權限（擁有者、群組、<code>rwx</code>）是自主存取控制（discretionary access control，DAC），由檔案的擁有者決定；兩層都通過，存取才成立。Linux 上常見的兩種實作是 SELinux（Oracle Linux、RHEL、Fedora 預設啟用）與 AppArmor（Ubuntu、Debian 預設啟用）。兩者的拒絕紀錄都要從 log 裡讀：SELinux 寫進 audit log，AppArmor 寫進核心訊息，核心訊息經由 <a href="/blog/linux/dotfile/knowledge-cards/journald/" data-link-title="journald（systemd-journald）" data-link-desc="用 journalctl 查不到重開機之前的紀錄、journal 佔掉多少磁碟不清楚、某個服務的 log 出現 Suppressed 訊息，或要判斷一筆紀錄該去 journal 還是 /var/log 底下的檔案找時">journald（systemd-journald）</a> 用 <code>journalctl -k</code> 查得到。</p>
<h2 id="概念位置">概念位置</h2>
<p>MAC 在一台對外服務的主機上負責的是程序被入侵之後的範圍：nginx 的程序被利用時，DAC 只看它以哪個使用者執行，MAC 另外把它限制在政策准許 web 伺服器碰的檔案與連接埠裡。代價是一類 DAC 看不出原因的拒絕：<code>ls -l</code> 的權限都對、程式也找得到檔案，存取照樣失敗。長期上線的主機上，拒絕紀錄要定期讀（AppArmor 的那一份在 <a href="/blog/linux/dotfile/knowledge-cards/journald/" data-link-title="journald（systemd-journald）" data-link-desc="用 journalctl 查不到重開機之前的紀錄、journal 佔掉多少磁碟不清楚、某個服務的 log 出現 Suppressed 訊息，或要判斷一筆紀錄該去 journal 還是 /var/log 底下的檔案找時">journald（systemd-journald）</a> 收下的核心訊息裡），正常值是零，見 <a href="/blog/devops/operations/09-host-lifecycle/long-running-host-maintenance/" data-link-title="長期上線主機的維護項目：會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算" data-link-desc="單台 Linux 主機（VPS、雲端主機）長期上線時，沒有人操作也會改變的狀態與維護對它們做的事：log 與資料的寫入者和清理機制（logrotate、journald、容器 log）、磁碟寫滿時各元件的反應、每份 log 要讀的欄位、入口在主機與雲端各層的盤點（ss）、各條更新管道的安全更新與重開機（dnf-automatic、unattended-upgrades、needs-restarting）、會到期與會改變的設定、檢查間隔的推算，以及接手一台已上線的主機時先處理哪幾項">長期上線主機的維護項目：會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算</a>〈定期讀的 log：每一份要看的欄位與它代表的狀態〉；從備份還原之後要核對的標籤，見 <a href="/blog/devops/operations/09-host-lifecycle/suspend-and-resume-service/" data-link-title="服務暫停與恢復上線：只停服務、停止主機、終止主機到全部刪除各自保留的資源，暫停期間仍計費與仍被指向的資源，恢復前要查的漂移與恢復的順序" data-link-desc="一台主機上的服務要暫停一段時間再恢復：只停服務、停止主機、終止主機保留設定、全部刪除靠 repo 重建各自保留什麼與付出什麼，怎麼依保留的月費、重建的成本與漂移量選擇，暫停期間仍在計費的資源（含在作業系統裡 shutdown 不停止計費），仍被 DNS 與 CDN 指向的結果（Cloudflare 521、522，釋放 IP 之後的懸空 DNS），恢復前要重新確認的漂移，以及依元件依賴排出的恢復順序與驗證">服務暫停與恢復上線：只停服務、停止主機、終止主機到全部刪除各自保留的資源，暫停期間仍計費與仍被指向的資源，恢復前要查的漂移與恢復的順序</a>。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>服務回 403 或 permission denied，而 <code>ls -l</code> 顯示的擁有者與權限允許這次存取。</li>
<li>SELinux：<code>getenforce</code> 印出 <code>Enforcing</code>（拒絕並記錄）、<code>Permissive</code>（只記錄不拒絕）或 <code>Disabled</code>；拒絕紀錄寫進 audit log，<code>ausearch -m AVC -ts recent</code> 列出最近的 <code>avc: denied</code> 紀錄，每一筆寫出被拒絕的動作、程序的標籤（<code>scontext</code>）與目標的標籤（<code>tcontext</code>）。</li>
<li>AppArmor：<code>aa-status</code> 列出載入的設定檔與它們的模式；拒絕紀錄在核心 log 裡，<code>journalctl -k | grep 'apparmor=&quot;DENIED&quot;'</code> 列得出來。</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>SELinux 的判斷依據是標籤：每個檔案、程序、連接埠都帶一個標籤，<code>ls -Z</code> 與 <code>ps -Z</code> 讀得到，政策寫的是標籤之間的規則（web 伺服器的程序 <code>httpd_t</code> 可以讀 <code>httpd_sys_content_t</code> 的檔案）。所以 SELinux 的拒絕多半是標籤不對，而標籤錯掉最常發生在檔案搬動的時候：</p>
<ul>
<li><strong><code>mv</code> 保留原本的標籤</strong>：從家目錄 <code>mv</code> 到 <code>/var/www</code> 的檔案仍然帶著家目錄的標籤，nginx 讀不到；<code>cp</code> 建的是新檔，取目的地目錄的預設標籤。</li>
<li><strong>從備份還原</strong>：<code>tar</code> 沒有帶 <code>--selinux</code> 這類保留延伸屬性的選項、或備份存在不支援延伸屬性的位置時，還原出來的檔案不帶原本的標籤。</li>
<li><strong>修法是依政策重設標籤，不是關掉 SELinux</strong>：<code>restorecon -Rv &lt;目錄&gt;</code> 依政策把檔案的標籤改回那個路徑的預設；網站放在政策不認得的路徑時，先用 <code>semanage fcontext</code> 登記那個路徑該有的標籤，再跑 <code>restorecon</code>。程序要做政策預設不准的事（nginx 往外連到後端）時，改的是對應的布林開關（<code>setsebool -P httpd_can_network_connect on</code>）。<code>setenforce 0</code> 把整台主機切到 Permissive，拒絕全部消失，防護也一起消失，只適合用來確認問題是不是 SELinux 造成的。</li>
</ul>
<p>AppArmor 的判斷依據是路徑：每個程式的設定檔寫出它能存取哪些路徑，不帶標籤，所以搬動檔案不會改變它的判斷；網站放到設定檔沒寫的路徑時，改的是那個程式的設定檔。</p>
<p>這張卡的指令與行為取自 SELinux 與 AppArmor 的官方文件與 RHEL 的 SELinux 指南，沒有在本站的主機上逐一實跑。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/linux/dotfile/knowledge-cards/journald/" data-link-title="journald（systemd-journald）" data-link-desc="用 journalctl 查不到重開機之前的紀錄、journal 佔掉多少磁碟不清楚、某個服務的 log 出現 Suppressed 訊息，或要判斷一筆紀錄該去 journal 還是 /var/log 底下的檔案找時">journald（systemd-journald）</a></li>
<li><a href="/blog/linux/dotfile/knowledge-cards/docker-named-volume-ownership/" data-link-title="Docker Named Volume Ownership（掛載點擁有者）" data-link-desc="container 內非 root 使用者寫不進掛載的 named volume、出現 permission denied 時回來讀">Docker Named Volume Ownership（掛載點擁有者）</a></li>
</ul>
]]></content:encoded></item></channel></rss>