{"id":246,"date":"2026-10-13T02:31:06","date_gmt":"2026-10-13T02:31:06","guid":{"rendered":"http:\/\/www.scmgalaxy.com\/tutorials\/2010\/04\/20\/how-to-configure-and-use-ssh-authentication-system-server-cvs\/"},"modified":"2026-10-13T02:31:06","modified_gmt":"2026-10-13T02:31:06","slug":"how-to-configure-and-use-ssh-authentication-system-server-cvs","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/how-to-configure-and-use-ssh-authentication-system-server-cvs\/","title":{"rendered":"Running a Legacy CVS Server over SSH on FreeBSD: Secure Setup and Hardening"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">CVS is a legacy version control system, but some teams still maintain historical codebases that depend on it. This guide explains how to run a <strong>CVS server over SSH authentication<\/strong> on FreeBSD, with a focus on practical hardening: key-only access, forced commands, and controlled repository permissions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Originally published on 2010-04-20; fully revised and updated.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2010\/04\/ssh-authentication-system-s.png\" alt=\"ssh-with-cvs\" width=\"600\" height=\"400\" \/><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What this guide covers<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The original post mixed system bootstrap, CVS setup, and SSH hardening. This revision separates those tasks so you can apply them safely in a maintenance environment.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Create and initialize a CVS repository directory<\/li><li>Require SSH key authentication for CVS users<\/li><li>Force SSH sessions to run only the CVS server command<\/li><li>Add CVSROOT-level ACL and commit-mail basics<\/li><li>Optionally expose repositories through cvsweb<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">CVS over SSH architecture<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The security model is straightforward: users authenticate with SSH keys, and each key is restricted to run a single CVS server command against an allowed repository path.<\/p>\n\n\n\n<pre class=\"mermaid\">\nflowchart LR\n    A[Developer generates SSH key] --> B[Admin installs key in authorized_keys]\n    B --> C[Developer runs CVS command over SSH]\n    C --> D[SSHD validates key]\n    D --> E[Forced CVS server command runs]\n    E --> F[Repository under home ncvs is served]\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Prepare the FreeBSD host (legacy baseline)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The source material targets older FreeBSD releases and ports layouts. Treat these commands as a legacy baseline and adapt package names\/paths for your current release.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Install required components referenced in the original flow<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php language-bash\">pkg_add -r cvsup-without-gui\ncd \/usr\/ports\/lang\/perl5<span class=\"hljs-number\">.8<\/span> &amp;&amp; make all install clean &amp;&amp; rehash\n<span class=\"hljs-keyword\">use<\/span>.<span class=\"hljs-title\">perl<\/span> <span class=\"hljs-title\">port<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Code bootstrap methods mentioned in the original<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Method<\/th><th>Command path<\/th><th>Notes<\/th><\/tr><\/thead><tbody><tr><td>FreeBSD source sync via CVS<\/td><td>cvs -d :pserver:&#8230; login\/export<\/td><td>No X11 dependency; endpoint and tags are legacy<\/td><\/tr><tr><td>Install cvsup-without-gui<\/td><td>pkg_add -r cvsup-without-gui<\/td><td>Historically faster sync path; package availability may vary<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php language-bash\"><span class=\"hljs-comment\"># Method 1: CVS-based bootstrap (legacy example)<\/span>\ncvs -d :pserver:anoncvs@anoncvs.jp.FreeBSD.org:\/home\/ncvs login\ncd \/usr\ncvs -R -d :pserver:anoncvs@anoncvs.jp.FreeBSD.org:\/home\/ncvs export -r RELENG_5_2 src ports\n\n<span class=\"hljs-comment\"># Method 2: cvsup package (legacy example)<\/span>\npkg_add -r cvsup-without-gui<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Harden SSH before enabling CVS access<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The original guidance explicitly enforces SSH protocol 2 and disables password authentication. This is the minimum baseline for a key-driven CVS setup.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php language-bash\"><span class=\"hljs-comment\"># \/etc\/ssh\/sshd_config<\/span>\nProtocol <span class=\"hljs-number\">2<\/span>\nPasswordAuthentication no<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Create the repository path and initialize CVSROOT<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use a dedicated group (for example <strong>ncvs<\/strong>) and a repository manager account (for example <strong>repoman<\/strong>) to separate administration from normal user logins.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs language-bash\">rm -rf \/home\/ncvs\nmkdir -p \/home\/ncvs\nchown -R repoman:ncvs \/home\/ncvs\nchmod -R 775 \/home\/ncvs\n\nsu -l repoman\ncvs -d \/home\/ncvs init<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Restrict CVS users with forced commands in authorized_keys<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">SSH accounts can become shell-access paths if unrestricted. The original method mitigates this by forcing each key to execute only the CVS server command.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php language-bash\"><span class=\"hljs-comment\"># ~\/.ssh\/authorized_keys for a CVS user<\/span>\ncommand=<span class=\"hljs-string\">\"\/usr\/bin\/cvs --allow-root=\/home\/ncvs server\"<\/span>,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA... user@example<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The original post showed <strong>ssh-dss<\/strong> keys. In modern OpenSSH deployments, DSA keys are commonly disabled; use a currently supported key type in your environment.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Configure commit mail and ACL-related files in CVSROOT<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The source references FreeBSD CVSROOT hook patterns for commit notification and basic access control. At minimum, validate these file roles in your repository:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>avail<\/strong>: controls module\/path access by user group<\/li><li><strong>access<\/strong>: controls who can perform CVS operations<\/li><li><strong>exclude<\/strong>: excludes paths from tag checks<\/li><li><strong>options<\/strong>: tag\/keyword behavior options<\/li><\/ul>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php language-perl\"><span class=\"hljs-comment\"># Example values referenced in cfg_local.pm style configuration<\/span>\n$MAILADDRS = <span class=\"hljs-string\">'cvs-all@example.org'<\/span>;\n$MAIL_BRANCH_HDR = <span class=\"hljs-string\">'X-Phantasm-CVS-Branch'<\/span>;\n$MAILBANNER = <span class=\"hljs-string\">'The Phantasm Studio repository'<\/span>;\n$CVSWEB_URL = <span class=\"hljs-string\">'http:\/\/cvsweb.example.org\/cgi-bin\/cvsweb.cgi'<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Set up cvsweb for browser access<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The original article installs cvsweb from ports and then registers repositories in <strong>cvsweb.conf<\/strong>. It also points to the FreeBSD CVSweb project page for newer versions: <a href=\"http:\/\/www.freebsd.org\/projects\/cvsweb.html\">http:\/\/www.freebsd.org\/projects\/cvsweb.html<\/a>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php language-bash\">cd \/usr\/ports\/devel\/cvsweb &amp;&amp; make all install clean\ncd \/usr\/local\/etc\/cvsweb\nvi cvsweb.conf\n\n<span class=\"hljs-comment\"># Add repository mapping (example)<\/span>\n<span class=\"hljs-comment\"># @CVSrepositories = (<\/span>\n<span class=\"hljs-comment\">#   'ncvs' =&gt; &#91;'New CVS Repository', '\/home\/ncvs'],<\/span>\n<span class=\"hljs-comment\"># );<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Comparison of access patterns in this setup<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Access pattern<\/th><th>Security level<\/th><th>Operational impact<\/th><th>Typical use<\/th><\/tr><\/thead><tbody><tr><td>SSH keys + forced CVS command<\/td><td>Strongest in this guide<\/td><td>Requires key distribution and per-key controls<\/td><td>Committer access to legacy repositories<\/td><\/tr><tr><td>SSH password login<\/td><td>Weaker<\/td><td>Simpler onboarding but larger attack surface<\/td><td>Not recommended for CVS commit paths<\/td><\/tr><tr><td>Anonymous pserver (for sync\/fetch)<\/td><td>Limited and legacy<\/td><td>Useful for historical anonymous export flows<\/td><td>Read-oriented bootstrap tasks only<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Best practices for legacy CVS operations<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>Keep CVS on an isolated host or VM with minimal services enabled.<\/li><li>Use key-only SSH authentication and forced commands for every committer key.<\/li><li>Back up repository data and CVSROOT hook\/config files together.<\/li><li>Document all local patches and custom hooks before OS\/package upgrades.<\/li><li>Plan a migration path to a modern VCS where possible; treat CVS as maintenance-only infrastructure.<\/li><\/ul>\n","protected":false},"excerpt":{"rendered":"<p>This guide rebuilds a classic CVS-over-SSH setup into a clear, security-focused runbook for FreeBSD environments. It covers repository initialization, SSH key restrictions, ACL\/commit mail basics, and optional cvsweb access.<\/p>\n","protected":false},"author":1,"featured_media":4374,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[20],"tags":[254,4133,369,4136,1122,2974,3113,919,593,938,4135,896,732,4132,213,4134],"class_list":["post-246","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cvs-concurrent-version-system","tag-configure","tag-configure-cvs-with-ssh","tag-cvs","tag-cvs-with-ssh-configuration","tag-guide","tag-help","tag-how-to","tag-instructions","tag-process","tag-reference","tag-setup-cvs-through-ssh","tag-ssh","tag-steps","tag-system-server","tag-tutorial","tag-use-ssh-with-cvs"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/246","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=246"}],"version-history":[{"count":4,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/246\/revisions"}],"predecessor-version":[{"id":79076,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/246\/revisions\/79076"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media\/4374"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=246"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=246"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=246"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}