PHP에서 SSH를 통해 MySQL 서버에 연결
원격 리눅스 머신에 데이터베이스를 가지고 있는데 SSH와 PHP 기능을 이용해서 접속하고 싶습니다(현재는 SSH2 라이브러리를 사용하고 있습니다).mysql_connect를 사용하려고 했지만 이 기능을 사용하려고 하면 액세스할 수 없습니다(권한은 부여했지만).
$connection = ssh2_connect('SERVER IP', 22);
ssh2_auth_password($connection, 'username', 'password');
$tunnel = ssh2_tunnel($connection, 'DESTINATION IP', 3307);
$db = mysqli_connect('127.0.0.1', 'DB_USERNAME', 'DB_PASSWORD',
'dbname', 3307, $tunnel)
or die ('Fail: '.mysql_error());
"mysqli_connect()에서 매개 변수 6을 문자열로 예상합니다, 리소스가 제공됩니다."라는 오류가 발생했습니다.어떻게 해결할 수 있습니까?
SSH 터널 솔루션
보안을 위해 Jumpbox 프록시를 통해 MySQL 데이터베이스 서버에 SSH 터널을 설정합니다.
(A) GUI 도구
요구 사항에 따라 Visual Studio Code Forwarding a Port, TablePlus와 같이 SSH Tunneling이 내장된 GUI MySQL 클라이언트를 사용하거나 PuTTY를 사용하여 로컬 포트 포워딩을 설정할 수 있습니다.
macOS에서는 Secure Pipes나 TablePlus를 좋아합니다.
(B) 명령줄
1단계.
ssh -fNg -L 3307:10.3.1.55:3306 username@ssh-jumpbox.com
여기서 핵심은 ssh에 로컬 포트 포워딩을 요청한다는 '-L' 스위치입니다.
위의 3307번 포트를 사용하기로 했습니다.이 포트로 향하는 로컬 시스템의 모든 트래픽이 이제 ssh 클라이언트를 통해 주소의 호스트에서 실행 중인 ssh 서버로 '포트 전달'됩니다.ssh-jumpbox.com
.
Jumpbox ssh 프록시 서버가 트래픽을 해독하고 대신 MySQL 데이터베이스 서버에 대한 네트워크 연결을 설정합니다.10.3.1.55:3306
이 에는 MySQL 데이터베이스 서버는 Jumpbox의 내부 네트워크에서 들어오는 을 봅니다.MySQL 데이터베이스 서버는 Jumpbox의 내부 네트워크 주소에서 들어오는 연결을 봅니다.
IMT2000 3GPP - 로컬 포트 포워딩 구문
구문은 약간 까다롭지만 다음과 같이 볼 수 있습니다.
<local_workstation_port>:<database_server_addr_remote_end_of_tunnel>:<database_server_port_remote_end> username@ssh_proxy_host.com
다른 스위치에 관심이 있다면 다음과 같습니다.
f()
안 함N함)
-g (원격 호스트가 로컬 전달 포트에 연결할 수 있도록 허용)
개인 키 인증, 위에 (-i) 스위치 추가:
-i/path/to/private-key
2단계.
로컬 MySQL 클라이언트에게 시스템의 로컬 포트 3307(-h 127.0.0.0.1)을 통해 SSH 터널을 통해 연결하고, 이제 1단계에서 설정한 SSH 터널을 통해 전송된 모든 트래픽을 전달하도록 지시합니다.
mysql -h 127.0.0.1 -P 3307 -u dbuser -p passphrase
이제 클라이언트와 서버 간의 데이터 교환은 암호화된 SSH 연결을 통해 전송되며 안전합니다.
보안노트
데이터베이스 서버에 직접 터널링하지 마십시오.인터넷에서 직접 접근할 수 있는 데이터베이스 서버를 갖추는 것은 엄청난 보안 부담입니다.터널 대상 주소를 Jumpbox/Bastion Host의 인터넷 주소(1단계 예 참조)로 만들고 데이터베이스 대상을 원격 네트워크에 있는 데이터베이스 서버의 내부 IP 주소로 만듭니다.나머지는 SSH가 합니다.
3단계.
이제 PHP 애플리케이션을 다음과 연결합니다.
<?php
$smysql = mysql_connect( "127.0.0.1:3307", "dbuser", "passphrase" );
mysql_select_db( "db", $smysql );
?>
MySQL 연결을 위한 ssh 명령줄 터널링에 대해 자세히 설명한 Chris Snyder의 훌륭한 기사에 감사드립니다.
불행히도 php가 제공하는 ssh2 터널은 로컬 포트를 터널로 지정할 수 없기 때문에 원격 mysql 연결을 처리할 수 없는 것 같습니다(22번 포트 또는 원격 서버가 실행 중인 어떤 ssh 포트에서만 작동합니다.이에 대한 제 해결책은 다음을 통해 터널을 여는 것입니다.exec()
오퍼레이터와 평소와 같이 연결합니다.
exec('ssh -f -L 3307:127.0.0.1:3306 user@example.com sleep 10 > /dev/null');
$mysqli = new mysqli('127.0.0.1', 'user', 'password', 'database', '3307');
저도 같은 것을 찾고 있었지만, 외부 명령이 필요하지 않고 외부 프로세스를 관리하는 것을 선호합니다.그래서 어느 순간, 어떤 PHP 스트림에서도 작동할 수 있는 순수한 PHP MySQL 클라이언트를 작성하는 것이 얼마나 어려울 수 있을까 하는 생각이 들었습니다.MySQL 프로토콜 문서를 기반으로 했을 때 반나절 정도 걸렸습니다.
https://gist.github.com/UCIS/4e509915ed221660e58f5169267da004
SSH2 라이브러리 또는 다른 스트림과 함께 사용할 수 있습니다.
$ssh = ssh2_connect('ssh.host.com');
ssh2_auth_password($ssh, 'username', 'password');
$stream = ssh2_tunnel($ssh, 'localhost', 3306);
$link = new MysqlStreamDriver($stream, 'SQLusername', 'SQLpassword', 'database');
$link->query('SELECT * FROM ...')->fetch_assoc();
완전한 mysqli API를 구현하지는 않지만 모든 평문 질의와 함께 작동해야 합니다.이것을 사용하기로 결정하셨다면 조심해주세요, 아직 코드 테스트를 충분히 하지 않았고 문자열 탈출 코드도 검토하지 않았습니다.
대부분의 사용자가 이 작업을 수행하는 데 문제가 있는 이유는 mysql 서버의 사용자가 localhost의 IP 주소인 127.0.0.1이 아닌 "localhost"에서만 허용하도록 설정되어 있기 때문이라고 생각합니다.
다음 단계를 수행하여 이 작업을 수행할 수 있습니다.
1단계: 대상 사용자에게 127.0.0.1 호스트 허용
SSH는 일반적으로 서버에 로그인한 후 mysql root 사용자로 로그인한 후 다음 명령을 실행합니다.
GRANT ALL ON yourdbname.* TO yourdbuser@127.0.0.1 IDENTIFIED BY 'yourdbpassword';
물론 위의 127.0.0.1을 지정하는 것이 핵심입니다.
2단계: MySQL에 대한 로컬 SSH 터널 시작
이제 원격 MySQL 서버에 대한 로컬 SSH 터널을 다음과 같이 시작할 수 있습니다.
ssh -vNg -L 33306:127.0.0.1:3306 sshuser@remotehost.com
-v
ssh를 장황한 모드로 작동하게 해서 무슨 일이 일어나고 있는지 확인하는 데 도움이 됩니다.예를 들어 연결을 시도할 때 터미널 콘솔에 다음과 같은 디버깅 출력이 표시됩니다.
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug1: Connection to port 33306 forwarding to 127.0.0.1 port 3306 requested.
연결을 닫으면 다음과 같이 출력됩니다.
debug2: channel 2: is dead
debug2: channel 2: garbage collecting
debug1: channel 2: free: direct-tcpip: listening port 33306 for 127.0.0.1 port 3306, connect from 127.0.0.1 port 52112 to 127.0.0.1 port 33306, nchannels 3
-N
ssh를 명령어를 발행하지 않고 연결을 설정한 후에 그냥 기다립니다.
-g
원격 호스트에서 로컬 전달 포트에 연결할 수 있습니다.이것이 필요한지 확실하지 않지만 동일한 SSH 터널을 통해 여러 연결을 다중화하는 데 유용할 수 있습니다.
-L
이것은 로컬 포트를 지정하는 주요 매개 변수입니다.33306
원격 호스트의 로컬 IP 주소에 연결합니다.127.0.0.1
그리고 원격 호스트의 mysql 포트는 보통3306
.
이 후 필요한 모든 메커니즘/기타 매개 변수를 사용하여 SSH를 통해 원격 호스트에 연결할 수 있습니다.내 경우에는 내 컴퓨터에 구성된 키 파일을 사용합니다.~/.ssh/config
그래서 난 단지 특정할 필요가 있습니다.user@host
들어가기 위해.
이렇게 명령을 내리면 포그라운드에서 SSH가 실행되므로 쉽게 닫을 수 있습니다.Ctrl + C
. 백그라운드 프로세스에서 이 터널을 실행하려면 추가할 수 있습니다.-f
이렇게 하기 위해서.
3단계: PHP/기타 mysql 호환 메서드에서 연결
로컬 호스트의 위에서 실행 중인 SSH 터널은 마치 mysql이 실행 중인 것과 동일하게 동작합니다.127.0.0.1
. 포트를 사용합니다.33306
(triple 3 참조) 일반 포트에서 로컬 SQL 서버를 실행할 수 있습니다.이제 평소에 하던 대로 연결할 수 있습니다. 그.mysql
터미널의 명령어는 다음과 같습니다.
mysql -h 127.0.0.1 -P 33306 -u yourmysqluser -p
어디에-P
(capital P)는 SSH 터널의 로컬 끝이 연결을 수락하는 포트를 지정합니다.사용하는 것이 중요합니다.127.0.0.1
신 IP 대신 localhost
왜냐하면 mysql cli는 아마도 리눅스 소켓을 사용하여 연결하려고 시도할 것이기 때문입니다.
PHP 연결 문자열의 경우 데이터 원본 이름 문자열(Yii2 구성의 경우)은 다음과 같습니다.
'dsn' => 'mysql:host=127.0.0.1;dbname=yourdbname;port=33306',
암호 및 사용자 이름은 일반으로 지정됩니다.
문서에 따르면 마지막 매개 변수는 '/var/run/mysql/mysql과 같은 소켓 또는 파이프 이름이어야 합니다.당신은 UNIX 소켓을 사용하여 연결하는 것이 아니기 때문에 당신에게는 해당되지 않습니다.그러니 그냥 빼놓도록 해보세요.
루트 자격 증명과 공개 개인 키 쌍 모두에서 ssh를 수행하여 시도했지만 명령줄을 통해 연결할 수 있지만 php 코드를 통해 연결할 수는 없습니다.터널도 만들어서(ssh2 기능을 사용해서) 시도했고, php 코드(시스템, exec 등)에서 셸 명령을 실행했지만 아무 것도 작동하지 않았습니다.드디어 shell command를 실행하기 위해 ssh2 기능을 시도해보았는데 드디어 작동하였습니다 :) 여기 코드가 있습니다, 만약 도움이 된다면:----
$connection = ssh2_connect($remotehost, '22');
if (ssh2_auth_password($connection, $user,$pass)) {
echo "Authentication Successful!\n";
} else {
die('Authentication Failed...');
}
$stream=ssh2_exec($connection,'echo "select * from zingaya.users where id=\"1606\";" | mysql');
stream_set_blocking($stream, true);
while($line = fgets($stream)) {
flush();
echo $line."\n";
}
특별히 php 기능을 사용하고 싶다면 이것을 시도해 볼 수 있었습니다.
연결 중인 사용자 이름과 암호에 올바른 호스트 이름 권한이 있는지 확인합니다.와일드카드는 '%'를 사용할 수 있다고 생각합니다.또한 로컬 네트워크에 없는 원격 컴퓨터에 연결하는 경우(Ssh into the local network 상에 있는 원격 컴퓨터에 연결하는 경우), 외부 트래픽을 위한 서버가 있는 라우터의 포트를 전달해야 연결할 수 있습니다.
ssh2를 사용하여 연결할 때, php가 ssh2를 사용하여 mysql에 연결할 수 있는 특별한 기능이 없기 때문에, 우리는 데이터 상호 작용을 위해 전통적인 socket과 mysql 프로토콜만 사용할 수 있습니다.
우리가 사용할 때.ssh2_tunnel
method, 생성이 성공하면 socket 개체가 반환됩니다. 그러면 이 socket 개체를 mysql 데이터 상호 작용에 사용할 수 있습니다.물론 우리는 mysql 프로토콜, 이른바 "악수 패킷"을 이해할 필요가 있습니다.
......
$tunnel = ssh2_tunnel(....)
......
//we need to construct the mysql data packet and then use fwrite to transfer this packet to mysql host
fwrite($tunnel, MYSQLDATAPACKET)
ssh2_connect 메서드를 사용하여 mysql 호스트를 연결하면 MySQL 핸드셰이크 패킷이 반환되며 다음과 같습니다.
a 5.5.5-10.3.34-MariaDB-cll-lve�WfyP`uKW����RtscuF:/}J7umysql_native_password!��
다음은 mysql 핸드셰이크 패킷 구조입니다.
size(byte) description
1 protocol version
n server version
4 connection id
8 auth-plugin-data-part-1
1 filler
2 capability flags
1 character set
2 status flags
2 capability flags
1 length of auth-plugin-data
10 reserved
13 auth-plugin-data-part-2
n auth-plugin name
그래서 우리는 그러한 구조를 사용하여 바이트 배열을 파싱해야 합니다.
Mysql handshake 를 받은 후, 우리는 mysql host 를 연결했고, 이제 mysql 로그인을 해야하기 때문에 mysql send data 패킷을 구성해야 합니다.
데이터 구조: (Handshake Response41)
4 capability flags, CLIENT_PROTOCOL_41 always set
4 max-packet size
1 character set
string[23] reserved (all [0])
string[NUL] username
if capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA {
lenenc-int length of auth-response
string[n] auth-response
} else if capabilities & CLIENT_SECURE_CONNECTION {
1 length of auth-response
string[n] auth-response
} else {
string[NUL] auth-response
}
if capabilities & CLIENT_CONNECT_WITH_DB {
string[NUL] database
}
if capabilities & CLIENT_PLUGIN_AUTH {
string[NUL] auth plugin name
}
if capabilities & CLIENT_CONNECT_ATTRS {
lenenc-int length of all key-values
lenenc-str key
lenenc-str value
if-more data in 'length of all key-values', more keys and value pairs
}
참고 항목: https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol :: 악수 응답41
좋아요, 그럼 mysql 호스트를 성공적으로 연결했고 데이터베이스를 조회할 수 있습니다.
다음은 Mysql 핸드셰이크 및 Mysql 소켓 로그인의 샘플 코드입니다.
function parseMysqlHandshakePack($hex_string)
{
$dataField = {
"protocol_version" => "",
"server_version" => "",
"thread_id" => "",
"salt1" => "",
"salt2" => "",
"salt" => "",
}
$dataField["protocol_version"] = UtiliHelper::HexToInt(UtiliHelper::HexSub($hex_string,0,1));
$dataField["server_version"] = UtiliHelper::HexToStr(UtiliHelper::HexSub($hex_string,1,7));
$dataField["thread_id"] = UtiliHelper::HexToInt(UtiliHelper::HexSub($hex_string,8,4));
$dataField["salt1"] = UtiliHelper::HexSub($hex_string,12,8);
$dataField["salt2"] = UtiliHelper::HexSub($hex_string,39,12);
$dataField["salt"] = $dataField["salt1"] . $dataField["salt2"];
return $dataField;
}
function constructMysqlLoginPacket($username, $password, $database, $salt){
$tags = [
"power_tag" => "",
"power_ext" => "",
"max_length" => "",
"charset" => "",
"fill_pad" => "",
"username" => "",
"password" => "",
"database" => "",
"client_auth_plugin" => "",
"payload" => ""
];
$tags['power_tag'] = "8da2";
$tags['power_ext'] = "0b00";
$tags['max_length'] = "000000c0";
$tags['charset'] = "08";
$tags['fill_pad'] = "0000000000000000000000000000000000000000000000";
$tags['client_auth_plugin'] = "6d7973716c5f6e61746976655f70617373776f726400";
$tags["payload"] = "150c5f636c69656e745f6e616d65076d7973716c6e64";
$tags['username'] = UtiliHelper::StrToHex($username)."0014";
$tags['password'] = UtiliHelper::encryptionPass($password,$salt);
$tags['database'] = UtiliHelper::StrToHex($database)."00";
$message = "";
foreach ($tags as $tagv){
$message .= $tagv;
}
return UtiliHelper::IntToHex(strlen($message)/2)."01".$message;
}
$salt
파라미터는 핸드셰이크 단계에서 나왔기 때문에, 당신은 mysql 핸드셰이크 메시지를 파싱하고 그 메시지를 받아야 합니다.salt
.
UtiliHelper
는 Github: https://github.com/gphper/PHPMysql/blob/master/src/UtiliHelper.php 의 서드파티 프로젝트에서 제공되는 사용자 정의 클래스입니다.
언급URL : https://stackoverflow.com/questions/464317/connect-to-a-mysql-server-over-ssh-in-php
'programing' 카테고리의 다른 글
C 포인터 및 배열: [Warning] 할당은 캐스트가 없는 정수에서 포인터를 만듭니다. (0) | 2023.10.21 |
---|---|
각도 사용범위 변수를 변경하지 않고 입력 필드의 형식을 지정하는 JS 지시문 (0) | 2023.10.21 |
URI에서 비트맵을 가져오는 방법? (0) | 2023.10.16 |
ASP .NET MVC 필드별 수준에서 클라이언트 측 유효성 검사 사용 안 함 (0) | 2023.10.16 |
JDBC Connection Pooling을 사용하고 있습니까? (0) | 2023.10.16 |