Programação Por Equipe DevBras

Capa do artigo: Campo "servidor" na tela de login: trocar a connection string é poder de administrador

Campo "servidor" na tela de login: trocar a connection string é poder de administrador

Tire a troca de banco da mão do usuário final: ambiente define a conexão, administrador escolhe de uma lista, o builder monta a string e tudo fica auditado.

O ERP desktop da distribuidora tem, na tela de login, um botão "Configurar servidor" com um campo de texto livre. Numa segunda-feira, uma pessoa do financeiro digita o servidor de homologação que viu num e-mail antigo e passa a manhã lançando pagamentos numa base de teste. Na semana seguinte, um prestador digita o IP de uma máquina dele, e o ERP, obediente, envia usuário e senha do SQL Server para lá.

A resposta curta: para onde o sistema se conecta é decisão de infraestrutura, não preferência de usuário. O ambiente (variável de ambiente, arquivo de configuração do servidor ou da instalação) define a conexão. Se um administrador precisa trocar, a troca é uma ação explícita, autenticada, escolhida de uma lista de servidores permitidos, montada com o builder do driver, testada antes de gravar e registrada em log. Nunca concatene texto do formulário numa connection string.

Por que um campo de servidor é perigoso

Uma connection string carrega quatro coisas sensíveis: para onde ir, em que banco entrar, com que credencial e com que segurança de transporte. Deixar o usuário final mexer nisso abre três problemas diferentes:

  • Base errada. Lançamentos em homologação somem; testes em produção viram dado real. Os dois erros são caros e difíceis de desfazer.
  • Credencial entregue a terceiros. Na autenticação SQL, o cliente envia usuário e senha ao servidor. Apontar o aplicativo para uma máquina controlada por outra pessoa entrega a credencial para ela.
  • Alcance da rede interna. Num sistema web, o servidor da aplicação faz a conexão. Um campo de servidor vira uma forma de fazer a sua aplicação testar portas e máquinas internas que o usuário não alcançaria.

Há ainda a injeção na própria string. Connection strings são pares chave=valor separados por ponto e vírgula, e, quando uma chave aparece mais de uma vez, vale a última. Se o código faz "Server=sql-prod;Database=" + banco e alguém digita vendas;Server=10.0.0.9 no campo de banco, o servidor muda sem que ninguém tenha tocado no campo de servidor.

Como tratar a conexão do jeito certo

  1. Tire o campo da tela de login. Usuário final escolhe, no máximo, a empresa ou filial, nunca o servidor.
  2. Defina a conexão por ambiente. Em aplicações web, por variável de ambiente ou cofre de segredos. Em aplicações desktop, num arquivo de configuração da instalação, protegido por permissão de administrador.
  3. Se a troca for necessária, crie uma tela de administração. Só para perfil administrativo, com nova autenticação, e com lista fechada de ambientes.
  4. Monte a string com o builder do driver. Ele escapa valores e impede que um campo vire outro.
  5. Teste antes de gravar. Abra a conexão com timeout curto e rode uma consulta simples. Falhou, não grava.
  6. Registre a mudança. Quem trocou, quando, de qual ambiente para qual, de que máquina. Sem a senha.
  7. Nunca mostre a senha de volta. A tela exibe o ambiente e o usuário, e a senha só pode ser substituída.

Exemplo: seletor de ambiente em C# com SqlConnectionStringBuilder

O código usa Microsoft.Data.SqlClient. Desde a versão 4 dessa biblioteca, a conexão é criptografada por padrão; não desligue a validação do certificado do servidor para "fazer funcionar".

using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Logging;

public sealed class SeletorDeAmbiente
{
    private static readonly IReadOnlyDictionary<string, string> Servidores =
        new Dictionary<string, string>
        {
            ["Produção"] = "sql-prod.distribuidora.local",
            ["Homologação"] = "sql-hml.distribuidora.local"
        };

    private readonly ILogger<SeletorDeAmbiente> _log;

    public SeletorDeAmbiente(ILogger<SeletorDeAmbiente> log) => _log = log;

    public static IEnumerable<string> Ambientes => Servidores.Keys;

    public static string Montar(string ambiente, string banco)
    {
        if (!Servidores.TryGetValue(ambiente, out var servidor))
            throw new ArgumentException("Ambiente não permitido: " + ambiente);

        var b = new SqlConnectionStringBuilder
        {
            DataSource = servidor,
            InitialCatalog = banco,
            IntegratedSecurity = true,   // conta do Windows, sem senha guardada
            ConnectTimeout = 5
        };
        return b.ConnectionString;
    }

    public async Task<string> TrocarAsync(string administrador, string ambienteAtual, string novoAmbiente, string banco)
    {
        string cs = Montar(novoAmbiente, banco);

        await using (var conn = new SqlConnection(cs))
        {
            await conn.OpenAsync();
            await using var cmd = new SqlCommand("SELECT 1", conn);
            await cmd.ExecuteScalarAsync();
        }

        _log.LogWarning(
            "Conexão alterada por {Administrador}: de {De} para {Para}, banco {Banco}",
            administrador, ambienteAtual, novoAmbiente, banco);

        return cs;   // quem chama grava na configuração protegida da instalação
    }
}

Repare no que o código não aceita: texto livre para servidor. A lista de ambientes é fixa e revisada em código. O nome do banco, que ainda vem de fora, passa pelo builder, que o trata como valor de InitialCatalog, e não como pedaço da string. Com IntegratedSecurity, a autenticação usa a conta do Windows e não há senha de banco para guardar ou exibir.

E nas aplicações web

Em ASP.NET Core, a string fica fora do código e da tela. A aplicação lê com builder.Configuration.GetConnectionString("Principal"), e o valor vem do arquivo de configuração do ambiente ou de uma variável ConnectionStrings__Principal definida no servidor ou no container. Trocar de banco é um deploy ou uma mudança de configuração revisada, não um formulário. O mesmo vale para segredos no container, como em segredo não entra na imagem do container.

Se a senha do banco já esteve num arquivo versionado ou numa tela, o primeiro passo é trocá-la, como explicado em segredo no Git se troca antes de limpar. E, para o conector em si, trave a versão no projeto em vez de depender da máquina.

Erros comuns

  • Campo de servidor livre na tela de login. Qualquer pessoa aponta o sistema para a base errada ou para uma máquina que coleta a credencial.
  • Concatenar valores do formulário na string. Um ponto e vírgula no campo de banco troca o servidor ou a credencial.
  • Gravar sem testar. A configuração quebrada derruba todos os usuários no próximo login.
  • Exibir a senha atual na tela de configuração. Quem passa atrás da cadeira leva a credencial do banco.
  • Desligar a validação do certificado para o erro sumir. A conexão passa a aceitar qualquer servidor que se apresente, inclusive um falso.
  • Trocar sem log. Quando os lançamentos aparecem na base errada, ninguém sabe quem mudou nem quando.

Checklist

  • A tela de login não tem campo de servidor nem de connection string.
  • A conexão vem de configuração do ambiente ou da instalação, protegida contra usuário comum.
  • A troca de ambiente, se existir, exige perfil administrativo e escolhe de uma lista fechada.
  • A string é montada com o builder do driver, nunca por concatenação.
  • A conexão é testada antes de a configuração ser gravada.
  • Cada troca gera um registro com autor, data, origem e destino, sem senha.
  • A validação do certificado do servidor continua ligada.

Entre no seu sistema com um usuário comum, sem privilégio nenhum: existe algum caminho, tela ou arquivo editável que faça a aplicação falar com outro servidor?

Avalie este artigo

Referência: https://social.msdn.microsoft.com/Forums/pt-BR/49ab691f-a4cb-4911-86bc-234946d7a855/mudar-connectionstring-em-tempo-de-execuo?forum=vscsharppt