Help us understand the problem. What is going on with this article?

ゼロからのSSHトンネル

背景

私はセキュリティ関係の製品の導入支援を生業にしていますが、仕事柄検証環境での動作確認を行うことが多々あります。近年弊社自身もクラウドサービスを提供していますし、弊社製品が他社製クラウドサービスと連携する機会も多くなって来ています。そんなこんなで、検証環境のネットワークに設置した機器とクラウドサービスの連携が必要になることがちょくちょくあります。でもいざ機器を用意しクラウドサービスと通信しようと試みると、検証ネットワーク内からクラウドサービスへの疎通がとれず、がっくしする様なこと、よくありませんか?私は年一位であります。

そんなとき、私がよく使うのがSSHトンネルです。今までは必要になったときに毎回ググっていろいろな先人達によって残された備忘録を紐解きながら試行錯誤してなんとかしているのですが、良い機会ですので一度ほぼほぼゼロからSSHトンネルを確立してそれを使用してアクセス制御を迂回する手順についてここに整理しておこうと思います。

なお、私自身2008年にこの仕事を始めてからずっとCentOS6を使用していて、SSHトンネルについてググって出てくる記事の多くもCentOS6ベースだったのですが、そろそろCentOS7もキャッチアップしないとなぁ、と思ったのでCentOS7ベースで書くことにしました。

やりたいこと

今回記載する手順で目指すのは、きつめのアクセス制限がかかった検証ネットワークに対して、ゆるめなアクセス制限がかかった箇所からSSH接続を行い、そのSSH接続を通して外部ネットワークに接続することです。具体的に今回の手順の前提になっている構成を以下に示します。

image.png

自分のPC内に構築するSSHクライアントから、検証ネットワーク内に存在するSSHサーバにSSHし、その接続で通信をトンネルにして検証ネットワーク内に存在する外部にアクセスしたいホストからインターネットへのアクセスを許可します。今回はグーグルが提供してくれているDNSサーバ8.8.8.8に通信しようと思います。(いつもPingしてスミマセン!)

手順

1. 事前準備

最初に、外部へのアクセスを行いたいホストの存在するネットワークと、自分のPC上に、CentOS7のホストを構築します。sshdとfirewalldしか使わないので、minimalで大丈夫のはずです。というか自分はminimalでやりました。参考までに以下作成したSSHサーバとSSHクライアントのNICの設定です。

SSHサーバ
[root@localhost ~]# nmcli connection show ens32
connection.id:                          ens32
connection.uuid:                        c9591804-16e8-4183-89a6-4237422e830a
connection.stable-id:                   --
connection.interface-name:              ens32
connection.type:                        802-3-ethernet
connection.autoconnect:                 yes
connection.autoconnect-priority:        0
connection.timestamp:                   1543473793
connection.read-only:                   no
connection.permissions:
connection.zone:                        --
connection.master:                      --
connection.slave-type:                  --
connection.autoconnect-slaves:          -1 (default)
connection.secondaries:
connection.gateway-ping-timeout:        0
connection.metered:                     unknown
connection.lldp:                        -1 (default)
802-3-ethernet.port:                    --
802-3-ethernet.speed:                   0
802-3-ethernet.duplex:                  --
802-3-ethernet.auto-negotiate:          yes
802-3-ethernet.mac-address:             --
802-3-ethernet.cloned-mac-address:      --
802-3-ethernet.generate-mac-address-mask:--
802-3-ethernet.mac-address-blacklist:
802-3-ethernet.mtu:                     auto
802-3-ethernet.s390-subchannels:
802-3-ethernet.s390-nettype:            --
802-3-ethernet.s390-options:
802-3-ethernet.wake-on-lan:             1 (default)
802-3-ethernet.wake-on-lan-password:    --
ipv4.method:                            manual
ipv4.dns:
ipv4.dns-search:
ipv4.dns-options:                       (default)
ipv4.dns-priority:                      0
ipv4.addresses:                         <SSHサーバのIPアドレス>/<マスク>
ipv4.gateway:                           <SSHサーバのデフォルトゲートウェイ>
ipv4.routes:
ipv4.route-metric:                      -1
ipv4.ignore-auto-routes:                no
ipv4.ignore-auto-dns:                   no
ipv4.dhcp-client-id:                    --
ipv4.dhcp-timeout:                      0
ipv4.dhcp-send-hostname:                yes
ipv4.dhcp-hostname:                     --
ipv4.dhcp-fqdn:                         --
ipv4.never-default:                     no
ipv4.may-fail:                          yes
ipv4.dad-timeout:                       -1 (default)
ipv6.method:                            auto
ipv6.dns:
ipv6.dns-search:
ipv6.dns-options:                       (default)
ipv6.dns-priority:                      0
ipv6.addresses:
ipv6.gateway:                           --
ipv6.routes:
ipv6.route-metric:                      -1
ipv6.ignore-auto-routes:                no
ipv6.ignore-auto-dns:                   no
ipv6.never-default:                     no
ipv6.may-fail:                          yes
ipv6.ip6-privacy:                       -1 (unknown)
ipv6.addr-gen-mode:                     stable-privacy
ipv6.dhcp-send-hostname:                yes
ipv6.dhcp-hostname:                     --
ipv6.token:                             --
GENERAL.NAME:                           ens32
GENERAL.UUID:                           c9591804-16e8-4183-89a6-4237422e830a
GENERAL.DEVICES:                        ens32
GENERAL.STATE:                          activated
GENERAL.DEFAULT:                        yes
GENERAL.DEFAULT6:                       no
GENERAL.VPN:                            no
GENERAL.ZONE:                           --
GENERAL.DBUS-PATH:                      /org/freedesktop/NetworkManager/ActiveConnection/0
GENERAL.CON-PATH:                       /org/freedesktop/NetworkManager/Settings/0
GENERAL.SPEC-OBJECT:                    /
GENERAL.MASTER-PATH:                    --
IP4.ADDRESS[1]:                         <SSHサーバのIPアドレス>/<マスク>
IP4.GATEWAY:                            <SSHサーバのデフォルトゲートウェイ>
IP4.ROUTE[1]:                           dst = 169.254.0.0/16, nh = 0.0.0.0, mt = 1002
IP6.ADDRESS[1]:                         fe80::81bf:7428:6134:6301/64
IP6.GATEWAY:
[root@localhost ~]#
SSHクライアント
[root@localhost ~]# nmcli connection show enp0s3
connection.id:                          enp0s3
connection.uuid:                        62e5a4e2-5878-4e26-b8c5-846fa250a4e7
connection.stable-id:                   --
connection.type:                        802-3-ethernet
connection.interface-name:              enp0s3
connection.autoconnect:                 yes
connection.autoconnect-priority:        0
connection.autoconnect-retries:         -1 (default)
connection.auth-retries:                -1
connection.timestamp:                   1543433603
connection.read-only:                   no
connection.permissions:                 --
connection.zone:                        public
connection.master:                      --
connection.slave-type:                  --
connection.autoconnect-slaves:          -1 (default)
connection.secondaries:                 --
connection.gateway-ping-timeout:        0
connection.metered:                     unknown
connection.lldp:                        default
802-3-ethernet.port:                    --
802-3-ethernet.speed:                   0
802-3-ethernet.duplex:                  --
802-3-ethernet.auto-negotiate:          no
802-3-ethernet.mac-address:             --
802-3-ethernet.cloned-mac-address:      --
802-3-ethernet.generate-mac-address-mask:--
802-3-ethernet.mac-address-blacklist:   --
802-3-ethernet.mtu:                     auto
802-3-ethernet.s390-subchannels:        --
802-3-ethernet.s390-nettype:            --
802-3-ethernet.s390-options:            --
802-3-ethernet.wake-on-lan:             default
802-3-ethernet.wake-on-lan-password:    --
ipv4.method:                            auto
ipv4.dns:                               --
ipv4.dns-search:                        --
ipv4.dns-options:                       ""
ipv4.dns-priority:                      0
ipv4.addresses:                         --
ipv4.gateway:                           --
ipv4.routes:                            --
ipv4.route-metric:                      -1
ipv4.route-table:                       0 (unspec)
ipv4.ignore-auto-routes:                no
ipv4.ignore-auto-dns:                   no
ipv4.dhcp-client-id:                    --
ipv4.dhcp-timeout:                      0 (default)
ipv4.dhcp-send-hostname:                yes
ipv4.dhcp-hostname:                     --
ipv4.dhcp-fqdn:                         --
ipv4.never-default:                     no
ipv4.may-fail:                          yes
ipv4.dad-timeout:                       -1 (default)
ipv6.method:                            auto
ipv6.dns:                               --
ipv6.dns-search:                        --
ipv6.dns-options:                       ""
ipv6.dns-priority:                      0
ipv6.addresses:                         --
ipv6.gateway:                           --
ipv6.routes:                            --
ipv6.route-metric:                      -1
ipv6.route-table:                       0 (unspec)
ipv6.ignore-auto-routes:                no
ipv6.ignore-auto-dns:                   no
ipv6.never-default:                     no
ipv6.may-fail:                          yes
ipv6.ip6-privacy:                       -1 (unknown)
ipv6.addr-gen-mode:                     stable-privacy
ipv6.dhcp-send-hostname:                yes
ipv6.dhcp-hostname:                     --
ipv6.token:                             --
proxy.method:                           none
proxy.browser-only:                     no
proxy.pac-url:                          --
proxy.pac-script:                       --
GENERAL.NAME:                           enp0s3
GENERAL.UUID:                           62e5a4e2-5878-4e26-b8c5-846fa250a4e7
GENERAL.DEVICES:                        enp0s3
GENERAL.STATE:                          activated
GENERAL.DEFAULT:                        yes
GENERAL.DEFAULT6:                       no
GENERAL.SPEC-OBJECT:                    --
GENERAL.VPN:                            no
GENERAL.DBUS-PATH:                      /org/freedesktop/NetworkManager/ActiveConnection/4
GENERAL.CON-PATH:                       /org/freedesktop/NetworkManager/Settings/1
GENERAL.ZONE:                           public
GENERAL.MASTER-PATH:                    --
IP4.ADDRESS[1]:                         10.0.2.15/24
IP4.GATEWAY:                            10.0.2.2
IP4.ROUTE[1]:                           dst = 0.0.0.0/0, nh = 10.0.2.2, mt = 100
IP4.ROUTE[2]:                           dst = 10.0.2.0/24, nh = 0.0.0.0, mt = 100
IP4.DNS[1]:                             <DNSサーバ>
IP4.DNS[2]:                             <DNSサーバ>
IP4.DOMAIN[1]:                          <ドメイン名>
DHCP4.OPTION[1]:                        requested_broadcast_address = 1
DHCP4.OPTION[2]:                        requested_domain_search = 1
DHCP4.OPTION[3]:                        requested_interface_mtu = 1
DHCP4.OPTION[4]:                        requested_time_offset = 1
DHCP4.OPTION[5]:                        requested_domain_name = 1
DHCP4.OPTION[6]:                        requested_rfc3442_classless_static_routes = 1
DHCP4.OPTION[7]:                        requested_classless_static_routes = 1
DHCP4.OPTION[8]:                        filename = centos7.pxe
DHCP4.OPTION[9]:                        requested_wpad = 1
DHCP4.OPTION[10]:                       domain_name = <ドメイン名>
DHCP4.OPTION[11]:                       next_server = 10.0.2.4
DHCP4.OPTION[12]:                       expiry = 1543497445
DHCP4.OPTION[13]:                       dhcp_message_type = 5
DHCP4.OPTION[14]:                       requested_subnet_mask = 1
DHCP4.OPTION[15]:                       dhcp_lease_time = 86400
DHCP4.OPTION[16]:                       routers = 10.0.2.2
DHCP4.OPTION[17]:                       ip_address = 10.0.2.15
DHCP4.OPTION[18]:                       requested_static_routes = 1
DHCP4.OPTION[19]:                       subnet_mask = 255.255.255.0
DHCP4.OPTION[20]:                       requested_nis_servers = 1
DHCP4.OPTION[21]:                       broadcast_address = 10.0.2.255
DHCP4.OPTION[22]:                       requested_ntp_servers = 1
DHCP4.OPTION[23]:                       requested_domain_name_servers = 1
DHCP4.OPTION[24]:                       domain_name_servers = <DNSサーバ>
DHCP4.OPTION[25]:                       requested_routers = 1
DHCP4.OPTION[26]:                       requested_ms_classless_static_routes = 1
DHCP4.OPTION[27]:                       requested_nis_domain = 1
DHCP4.OPTION[28]:                       requested_host_name = 1
DHCP4.OPTION[29]:                       network_number = 10.0.2.0
DHCP4.OPTION[30]:                       dhcp_server_identifier = 10.0.2.2
IP6.ADDRESS[1]:                         fe80::ff5b:badc:7af3:c4c5/64
IP6.GATEWAY:                            --
IP6.ROUTE[1]:                           dst = ff00::/8, nh = ::, mt = 256, table=255
IP6.ROUTE[2]:                           dst = fe80::/64, nh = ::, mt = 256
IP6.ROUTE[3]:                           dst = fe80::/64, nh = ::, mt = 100
[root@localhost ~]#

はい、IPアドレスの設定と、起動時に自動的にNICをUPさせる以外はデフォルトのままです。SSHクライアントは自信のPCにVirtualBoxをインストールしてそこで動かしています。VirtualBoxのNATネットワークではVitualBoxがDHCPを動かしていてくれるので、SSHクライアントではIPアドレスは静的に設定せずにDHCPで割当てる様にしています。
この辺の部分は利用される方の環境によっていろいろ異なるところだと思いますので、余り細かく書かないようにします。要件はただ一つ、SSHクライアントからSSHサーバに疎通がとれて、SSH接続できる様にしておいてください。

2. RSA認証でSSHするための準備

接続するたびに毎回ユーザ名パスワードを入力するのが面倒なので、SSH接続時の認証をRSAにします。
まず、SSHクライアント上でRSAキーペアを生成します。

SSHクライアント
[root@localhost ~]# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:4l6T8C/bmSPa7nBffpr4Fl4L+anBHExtTj/pmnmnmyo root@localhost.localdomain
The key's randomart image is:
+---[RSA 2048]----+
|                 |
|             .   |
|            . +  |
|           o + ..|
|      o S   o..o.|
|     . + . o+.o .|
|      o *  .== + |
|     . =o=E*ooOo.|
|      o+=+B=BO=o.|
+----[SHA256]-----+
[root@localhost ~]#

次にSSHクライアント上で生成したRSA公開鍵を表示して、SSHサーバの「/root/.ssh/authorized_keys」にコピーしてください。

SSHクライアント
[root@localhost ~]# cat /root/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwfKiyagK/4VB7nxUUFPUpK5LU2EM7d43g+T4xifHPfn801rUs7RqEN4DmTqVkzf5mIUJCDpgUJd9GytA/+oc0wxaezdYYlMSP3361j9LzQCmyZhZ6gc8reOliyWMkzqVz/qH97nAMhFIXLTWW3t+I+ts9sx/GNOqOkUAGlMN7dAeNmU+6olCX9ALEmbTLf3a3J1/ti1VOs5fyPhAUCxjqAD/RJZmNa5eVbfePNHFugDlxpUfxsIyfadWNGgbcYk53/JbIEwpiNMqBP6ZjIVKhMZTlZn28wg045NICxDBuZaLw6VYZTyD8Q61J2FRudvPrQMWOL86QZMEMLtHNMtgp root@localhost.localdomain
[root@localhost ~]#
SSHサーバ
[root@localhost ~]# cat /root/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwfKiyagK/4VB7nxUUFPUpK5LU2EM7d43g+T4xifHPfn801rUs7RqEN4DmTqVkzf5mIUJCDpgUJd9GytA/+oc0wxaezdYYlMSP3361j9LzQCmyZhZ6gc8reOliyWMkzqVz/qH97nAMhFIXLTWW3t+I+ts9sx/GNOqOkUAGlMN7dAeNmU+6olCX9ALEmbTLf3a3J1/ti1VOs5fyPhAUCxjqAD/RJZmNa5eVbfePNHFugDlxpUfxsIyfadWNGgbcYk53/JbIEwpiNMqBP6ZjIVKhMZTlZn28wg045NICxDBuZaLw6VYZTyD8Q61J2FRudvPrQMWOL86QZMEMLtHNMtgp root@localhost.localdomain
[root@localhost ~]#

毎回IPアドレスを打つのも面倒なので、SSHクライアント上でSSH接続先の事前定義をします。

SSHクライアント
[root@localhost ~]# cat /root/.ssh/config
host vpn
    HostName <SSHサーバのIPアドレス>
    IdentityFile ~/.ssh/.ssh/id_rsa

[root@localhost ~]#

上記の定義を行うことで、SSHクライアント上では接続先に「vpn」を指定するとに対して「~/.ssh/.ssh/id_rsa」に対応したキーペアを使用して接続してくれます。下の例ではポート番号3022を指定していますが、これは私の環境でIPアドレスが枯渇している関係で経路上でPATしているためですので、無視して頂いてOKです。

SSHクライアント
[root@localhost ~]# ssh -p 3022 vpn
Last login: Wed Nov 28 02:17:04 2018 from <SSHクライアントのIPアドレス>
[root@localhost ~]# exit
logout
Connection to <SSHサーバのIPアドレス> closed.
[root@localhost ~]#

3. SSH時にtunインタフェースが生成される様にする

SSH接続時に自動的にtunインタフェースが生成される様にSSHサーバとSSHクライアントの両方で設定変更をします。

最初に実施するのは、SSHサーバ上で、sshdの設定ファイルを編集して、トンネル接続を許可することです。PermitTunnelでは、point-to-point(L3接続、tunデバイスを使用)と、ethernet(L2接続、tapデバイスを使用)が選べるようですが、今回の用途的に別にL2接続は必要ないので、point-to-pointで接続します。設定ファイルの編集が完了したらsshdを再起動します。

SSHサーバ
[root@localhost ~]# cat /etc/ssh/sshd_config | grep Permit  
#PermitRootLogin yes    
#PermitEmptyPasswords no    
# the setting of "PermitRootLogin without-password".    
#PermitTTY yes  
#PermitUserEnvironment no   
#PermitTunnel no    
PermitTunnel point-to-point 
#   PermitTTY no
[root@localhost ~]# systemctl reload sshd   
[root@localhost ~]#

SSHトンネル確立時に、自動的にそのトンネルを使用する経路を登録するためにスクリプトを作ります。本当はデフォルトルートがトンネル向きに切れれば楽なのですが、そうすると多くの場合SSH自体が切れてしまいます。賢い人は何か良い方法を思いつくのかもしれませんが、仕方なく通信したい宛先を個別に経路情報を追加することにします。今回はgoogle DNSに通信を行いたいので、8.8.8.8/32への通信がトンネルインタフェースに向くようにエントリを追加します。
なお、今回はSSHサーバ、SSHクライアントの双方にtun0インタフェースが生成される様にし、それぞれ192.168.6.1、192.168.6.2というIPアドレスがアサインされる様にします。なので、SSHサーバからみると8.8.8.8/32への通信はtun0を使用して192.168.6.2に転送する様に設定することになります。

SSHサーバ
[root@localhost ~]# cat ./route-add.sh
#!/bin/sh

ip route add 8.8.8.8/32 via 192.168.6.2 dev tun0

[root@localhost ~]# chmod +x ./route-add.sh

「/root/.ssh/authorized_keys」ファイル内に、command=エントリを追加して、登録したRSAキーを使用してSSH接続が行われた際に自動的に実施されるコマンドを定義します。今回は、SSH接続により生成されるtun0インタフェースへのIPアドレスアサイン、tun0インタフェースの立ち上げ、経路追加スクリプトがSSH接続時に自動的に実行されるようにしました。

SSHサーバ
[root@localhost ~]# cat /root/.ssh/authorized_keys
command="ip addr add 192.168.6.1 peer 192.168.6.2 dev tun0; ip link set tun0 up; /root/route-add.sh" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwfKiyagK/4VB7nxUUFPUpK5LU2EM7d43g+T4xifHPfn801rUs7RqEN4DmTqVkzf5mIUJCDpgUJd9GytA/+oc0wxaezdYYlMSP3361j9LzQCmyZhZ6gc8reOliyWMkzqVz/qH97nAMhFIXLTWW3t+I+ts9sx/GNOqOkUAGlMN7dAeNmU+6olCX9ALEmbTLf3a3J1/ti1VOs5fyPhAUCxjqAD/RJZmNa5eVbfePNHFugDlxpUfxsIyfadWNGgbcYk53/JbIEwpiNMqBP6ZjIVKhMZTlZn28wg045NICxDBuZaLw6VYZTyD8Q61J2FRudvPrQMWOL86QZMEMLtHNMtgp root@localhost.localdomain
[root@localhost ~]#
SSHクライアント
[root@localhost ~]# cat /root/.ssh/config   
host vpn    
    HostName <SSHサーバのIPアドレス>
    IdentityFile ~/.ssh/id_rsa
    Tunnel point-to-point
    TunnelDevice 0:0
    PermitLocalCommand yes
    LocalCommand ip addr add 192.168.6.2 peer 192.168.6.1 dev tun0; ip link set tun0 up

[root@localhost ~]# 

ここまで出来たら難しいことを考えずに、後はip-forwardingを有効にして、firewalldを無効化して、戻り通信のための静的経路をたくさん追加して・・・で今までは対応していましたが、もういい加減内部のホストが増えるたびに戻り経路を書くのが面倒なのでfirewalldにPATしてもらうことで戻り経路を気にしなくても良くしようと思います。

4. firewalldの設定を変更し、通信の転送とPATが実施される様にする

SSHサーバの内部向けNICであるens32をtrustedゾーンに設定し、外部側のtun0をpublicゾーンに設定します。そしてpublicゾーンではPATを行う様に設定します。--permanentオプションをつけて再起動後も設定が維持されるようにします。

SSHサーバ
[root@localhost ~]# firewall-cmd --zone=public --change-interface tun0 --permanent
success
[root@localhost ~]# firewall-cmd --zone=trusted --change-interface ens32 --permanent
The interface is under control of NetworkManager, setting zone to 'trusted'.
success
[root@localhost ~]# firewall-cmd --zone=public --add-masquerade --permanent
success
[root@localhost ~]# firewall-cmd --reload
success
[root@localhost ~]#

同じように、SSHクライアント側では内側であるtun0をtrustedに設定し、publicゾーンではPATが行われる様にします。外側のNICであるenp0s3は自動的にpublicに割当てられていそうなので今回は設定変更していませんが、もし違うゾーンに設定されていそうであれば、publicに設定変更してください。

SSHクライアント
[root@localhost ~]# firewall-cmd --zone=trusted --change-interface=tun0 --permanent
The interface is under control of NetworkManager, setting zone to 'trusted'.
success
[root@localhost ~]# firewall-cmd --zone=public --add-masquerade --permanent
success
[root@localhost ~]# firewall-cmd --reload
success
[root@localhost ~]#

5. 動作確認

動作確認のために、外部にアクセスしたいホストとしてCisco Cloud Service Router 1000(CSR1K)を用意しました。まずはSSHトンネルを確立する前の状態で8.8.8.8にpingしてみます。
image.png
失敗します。

では、今回構築したSSHクライアントからSSHサーバにSSHトンネル接続を行います。(プロンプトは返ってこなくなるので、切断したいときはCtrl+Cを押下します)

SSHクライアント
[root@localhost ~]# ssh -p 3022 vpn

改めてCSR1Kから8.8.8.8にpingしてみます。
image.png
届くようになりました!これにて目標達成です!

おわりに

自分で使っておいて何ですが、SSHトンネル使うと管理者の意図しない通信が結構自由に出来てしまう可能性があります。自分なりにこういったことが自分の管理下のネットワークで行われた時にどのような対策が打てるかなぁと考えて見たのですが、通信としてはSSHとしか見えず、さらに暗号化されていて内容もわからないので、やはりNetwork as a Sensor(NaaS)等のソリューションでネットワーク上の振る舞い分析を行って、セッション維持時間、データ転送量を元に怪しいホストを検知するしかないような気がします。ということでこれからもCisco Stealthwatchをよろしくお願いします!

Cisco Network as a Sensor
https://www.cisco.com/c/ja_jp/solutions/enterprise-networks/enterprise-network-security/net-sensor.html

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away