Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Validando chave ssh pública no Laravel
Matheus Lopes Santos
Matheus Lopes Santos

Posted on

     

Validando chave ssh pública no Laravel

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']
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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çãoexec, 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 comandossh-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/
Enter fullscreen modeExit fullscreen mode

storage/app/ssh/.gitignore

*!.gitignore
Enter fullscreen modeExit fullscreen mode

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();}}
Enter fullscreen modeExit fullscreen mode

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();});
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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.');}}}
Enter fullscreen modeExit fullscreen mode

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(),],
Enter fullscreen modeExit fullscreen mode

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);});
Enter fullscreen modeExit fullscreen mode

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)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
medanielsantos profile image
Daniel Henrique
  • Joined

Conteúdo de Top Demais, Parabéns meu querido

CollapseExpand
 
devlopez profile image
Matheus Lopes Santos
I'm a guy passionate about PHP and web development. I enjoy playing music, cycling, and watching some movies from time to time.
  • Work
    Tech Lead at DevSquad
  • Joined

Valeu meu nobre ❤️

CollapseExpand
 
z4nder profile image
Alexandre
  • Joined

Boa Matheusão, conteúdo muito top

CollapseExpand
 
devlopez profile image
Matheus Lopes Santos
I'm a guy passionate about PHP and web development. I enjoy playing music, cycling, and watching some movies from time to time.
  • Work
    Tech Lead at DevSquad
  • Joined

Obrigado meu nobre ❤️

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I'm a guy passionate about PHP and web development. I enjoy playing music, cycling, and watching some movies from time to time.
  • Work
    Tech Lead at DevSquad
  • Joined

More fromMatheus Lopes Santos

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp