
Quando somos chamados para desenvolver uma aplicação, devemos ter em mente que podemos ter que lidar com vários tipos de problemas, aqueles que talvez jamais tenhamos imaginado em enfrentar. Contudo, vez ou outra precisamos sair de nossa zona de conforto.
Entendendo o problema
Há alguns dias, fui acionado para construir uma feature que iria receber a chave pública de um dev e iria enviá-la, posteriormente, para o forge, fazendo com que o usuário tivesse acesso ssh aos devidos servidores.
Maravilha Matheusão, como vou validar essa tipo de dado?
Incialmente a gente pensa em validar o básico, como o tamanho da string, se ela já existe no banco de dados, etc:
'ssh_key'=>['nullable','string','unique:users,ssh_key','max:5000']
Beleza, mas e se o meu usuário passar, sei lá, todas as letras do alfabeto? Infelizmente vai passar pela validação 🙁.
À procura da validação perfeita
Pesquisei bastante à respeito de como realizar essa validação. Em vários blogs, vi muita gente indicando usar funções nativas, como oopenssl_verify
,openssl_get_publickey
ou aopenssl_pkey_get_details
, mas elas infelizmente não funcionaram para o que eu precisava (Lembre-se, uma chave SSH é diferente de uma chave SSL, por isso essas funções não funcionam). Vi em outros fóruns um pessoal dizendo para utilizar o packagehttps://phpseclib.com/. Mas para pra pensar, pra que instalar um pacote que você só vai utilizar uma classe e somente um método dela?
Eu vejo isso como um acoplamento totalmente desnecessário, mas enfim…
Indo um pouco mais fundo
Depois de alguma pesquisa, vi que podemos usar ossk-keygen
para validar essa string pra gente, mas como?
Para isso, podemos usar duas flags, a-l
para pegar o fingerprint e o-f
para indicar o caminho do arquivo. Então o nosso comando ficaria assim:
ssh-keygen-lf /path/to/my/file.pub
E dessa forma poderemos checar se nossa chave SSH é válida ou não.
Criando nosso comando de checagem
O Laravel trouxe, a partir da versão 10, um componente chamadoProcess, que nada mais é do que um wrapper ao redor do componente Process doSymfony. É com esse carinha que vamos fazer a mágica.
Logicamente que poderíamos usa a função
exec
, nativa do php. Porém, se você acha que não é necessário usar este wrapper, fique à vontade 🙂👍🏻.
Vamos pensar o que precisamos fazer:
- Preciso receber a string contendo a chave do usuário.
- Preciso salvar essa string em algum lugar acessível.
- Preciso chamar o comando
ssh-keygen
com o caminho do arquivo. - Preciso destruir o arquivo após a validação.
Configurando as Coisas
Vamos criar um diretório dentro destorage/app
chamadossh
. Não se esqueça de remover esse novo diretório do seu versionamento:
storage/app/.gitignore
*!public/!.gitignore!ssh/
storage/app/ssh/.gitignore
*!.gitignore
Escrevendo nossa classe
Agora podemos criar a nossa classe que fará a interação com ossh-keygen
App/Terminal/ValidateSsh.php
<?phpdeclare(strict_types=1);namespaceApp\Terminal;useIlluminate\Support\Facades\Process;useIlluminate\Support\Str;classValidateSsh{privatestring$keyPath;publicfunction__construct(privatereadonlystring$content){$this->keyPath=storage_path('app/ssh/'.Str::uuid().'.pub');file_put_contents($this->keyPath,$this->content);}publicfunction__invoke():bool{returnProcess::run(command:'ssh-keygen -lf '.$this->keyPath.' && rm '.$this->keyPath,)->successful();}}
Maravilha, a nossa classe está prontinha pra ser utilizada.
- Recebe o conteúdo e salva com um nome aleatório.
- Checa o arquivo e em caso de sucesso, já o apaga também.
Agora, vamos escrever os nossos testes.
tests/Unit/Terminal/ValidateSshTest.php
<?phpdeclare(strict_types=1);useApp\Terminal\ValidateSsh;it('should return true if process if file is valid',function(string$key){$validateSsh=newValidateSsh($key);expect($validateSsh())->toBeTruthy();})->with(['RSA'=>'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQD3vYKSuh7rJf+NtWn04CFyT9+nmx+i+/sP+yMN9ueJ+Rd5Ku6d9kgscK2xwlRlkcA0sethslu0WUsG81RC1lVpF6iLrc/9O45ZhEY1CB/7dofr+7ZNwu/DJtbW6YE7oyT5G97BUW763TMq/YO9/xjMToetElTEJ4hUVWdP8q93b3MVHBazk2PEuS05wzP4p5XeQnhKq4LISetJFEgI8Y+HEpK29GiU/18fhaGZvdVwOToOxTwEwBbS3fTLNkBaUTWw9q3i7S60RRncBCHppcs2irrzw7yt7ZQOnut/BIjIGESoxx+N4ZrpTmX6P5d3/9Duk40Mfwh1ftsvze6o5AW4Xi0tki8b6bsMXmO7SapqVdiMZ5/4BWOkqHWhi926qz7I9NWoZuVFAUpSoe6fObzQBRooVp7ARw7gJ4C+Q4xc1gJJkZoQ/Wj/wHkVnbLw9M5+t5GjyWgDDOr5iyoGOyIwhuEFvATzIYH0z5B6anL1n6XQmeGh5OWKJN8wE5qVNTU= worker@envoyer.io','EDCSA'=>'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFvXWSVYzRnjxYsz/xKjOjAaPjzg98MMHaDulQYczTX28xlsMmFkviCeCCv7CLh19ydoH4LNKpvgTGiMXz8ib68= worker@envoyer.',]);it('should return false if ssh file is invalid',function(){$validateSsh=newValidateSsh('a simple text file');expect($validateSsh())->toBeFalsy();});
Escrevendo nossa rule
Pensa que acabou? Não mesmo. A responsabilidade da classeValidateSsh
é apenas a de verificar se a chave é válida ou não.
Vamos criar uma rule para que possamos fazer uso dessa validação.
php artisan make:rule IsSshKeyValid
Maravilha, agora, podemos fazer o seguinte:
<?phpdeclare(strict_types=1);namespaceApp\Rules;useApp\Terminal\ValidateSsh;useClosure;useIlluminate\Contracts\Validation\ValidationRule;useIlluminate\Translation\PotentiallyTranslatedString;classIsSshKeyValidimplementsValidationRule{/** * @param Closure(string): PotentiallyTranslatedString $fail */publicfunctionvalidate(string$attribute,mixed$value,Closure$fail):void{$validateSsh=newValidateSsh($value);if(!$validateSsh()){$fail('The :attribute is not a valid SSH key.');}}}
Com isso, já estamos prontos para fazer os nossos testes http ❤️
Testando nosso nossa chamada Http
Antes de prosseguir para os testes http, precisamos adicionar a nossa rule em nossas regras de validação:
'ssh_key'=>['nullable','string','unique:users,ssh_key','max:5000',newIsSshKeyValid(),],
E nossos testes para esse campo podem ficar dessa maneira:
it('should validate `ssh_key` field',function(mixed$value,string$error){login();postJson(route('api.users.store'),['ssh_key'=>$value])->assertUnprocessable()->assertJsonValidationErrors(['ssh_key'=>$error]);})->with([fn()=>[5000,__('validation.string',['attribute'=>'ssh key'])],fn()=>[str_repeat('a',5001),__('validation.max.string',['attribute'=>'ssh key','max'=>5000])],fn()=>['aa','The ssh key is not a valid SSH key.'],function(){$user=User::factory()->create(['ssh_key'=>'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQD3vYKSuh7rJf+NtWn04CFyT9+nmx+i+/sP+yMN9ueJ+Rd5Ku6d9kgscK2xwlRlkcA0sethslu0WUsG81RC1lVpF6iLrc/9O45ZhEY1CB/7dofr+7ZNwu/DJtbW6YE7oyT5G97BUW763TMq/YO9/xjMToetElTEJ4hUVWdP8q93b3MVHBazk2PEuS05wzP4p5XeQnhKq4LISetJFEgI8Y+HEpK29GiU/18fhaGZvdVwOToOxTwEwBbS3fTLNkBaUTWw9q3i7S60RRncBCHppcs2irrzw7yt7ZQOnut/BIjIGESoxx+N4ZrpTmX6P5d3/9Duk40Mfwh1ftsvze6o5AW4Xi0tki8b6bsMXmO7SapqVdiMZ5/4BWOkqHWhi926qz7I9NWoZuVFAUpSoe6fObzQBRooVp7ARw7gJ4C+Q4xc1gJJkZoQ/Wj/wHkVnbLw9M5+t5GjyWgDDOr5iyoGOyIwhuEFvATzIYH0z5B6anL1n6XQmeGh5OWKJN8wE5qVNTU= worker@envoyer.io',]);return[$user->ssh_key,__('validation.unique',['attribute'=>'ssh key'])];},]);it('should store an user',function(){login();$data=['name'=>'Matheus Santos','email'=>'matheusao@my-company.com','ssh_key'=>'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQD3vYKSuh7rJf+NtWn04CFyT9+nmx+i+/sP+yMN9ueJ+Rd5Ku6d9kgscK2xwlRlkcA0sethslu0WUsG81RC1lVpF6iLrc/9O45ZhEY1CB/7dofr+7ZNwu/DJtbW6YE7oyT5G97BUW763TMq/YO9/xjMToetElTEJ4hUVWdP8q93b3MVHBazk2PEuS05wzP4p5XeQnhKq4LISetJFEgI8Y+HEpK29GiU/18fhaGZvdVwOToOxTwEwBbS3fTLNkBaUTWw9q3i7S60RRncBCHppcs2irrzw7yt7ZQOnut/BIjIGESoxx+N4ZrpTmX6P5d3/9Duk40Mfwh1ftsvze6o5AW4Xi0tki8b6bsMXmO7SapqVdiMZ5/4BWOkqHWhi926qz7I9NWoZuVFAUpSoe6fObzQBRooVp7ARw7gJ4C+Q4xc1gJJkZoQ/Wj/wHkVnbLw9M5+t5GjyWgDDOr5iyoGOyIwhuEFvATzIYH0z5B6anL1n6XQmeGh5OWKJN8wE5qVNTU= worker@envoyer.io',];postJson(route('api.users.store'),$data)->assertCreated();assertDatabaseHas(Users::class,$data);});
Legal não?
Agora, posso cadastrar os usuários no meu sistema sem me preocupar com aqueles engraçadinhos que vão passar umaaaaaaaa
no campossh_key
😃.
E lembre-se, às vezes precisamos sair do óbvio para encontrar as soluções para alguns problemas. Quanto mais mente-aberta formos, mais rápido conseguimos progredir e aprender novas coisas.
Um abraço e até a próxima 😗 🧀
Top comments(4)

- WorkTech Lead at DevSquad
- Joined
Valeu meu nobre ❤️

- WorkTech Lead at DevSquad
- Joined
Obrigado meu nobre ❤️
For further actions, you may consider blocking this person and/orreporting abuse