miércoles, 29 de febrero de 2012

Cómo proteger tu servidor FileZilla Server en Internet

Dentro de mi espiral de paranoia relativa a la seguridad de mi nuevo servidor, hoy le toca el turno al servicio FTP.

Como servidor de FTP hemos seleccionado FileZilla Server.

Los pasos que hemos tomado para intentar garantizar la seguridad del servicio han sido los siguientes:

Cambiar el mensaje de bienvenida que, por defecto, muestra el nombre del programa y versión del mismo. Lo cambiamos para no dar más información y que esta pueda ser utiliza en nuestro contra a través de exploits de seguridad conocidos del programa y versión.

clip_image001

Activa el autobaneo para que si una misma IP intenta conectar erróneamente X veces dentro un periodo concreto, sea baneada automáticamente.

clip_image002

Activar el log para después poder analizarlo si es necesario.

clip_image003

Cambiar la cuenta de usuario con la que se ejecuta el servicio de FileZilla.

Para ello y según la documentación del producto, hay que crear un nuevo usuario y darle los siguientes permisos:

  • Escritura en el fichero FileZilla Server.xml.
  • Escritura en la carpeta \Logs del directorio de instalación.
  • Control total en el directorio raíz donde vayamos a alojar las carpetas compartidas del servicio.

Después cambiaremos la cuenta en servicios (por defecto se ejecuta con la cuenta SYSTEM que tiene excesivos privilegios):

clip_image004

clip_image005

Por último, tendremos que utilizar los servicios de cuota de Windows si queremos limitar el espacio máximo para las subidas de ficheros que se realicen a través del servicio FTP.

La verdad es que es una pena que FileZilla Server no tenga integrada esta característica, porque de hacerlo seguro que podríamos configurar la cuota por usuario de FTP en vez de sólo hacerlo para la cuenta de Windows en la que estamos ejecutando el servicio.

En el siguiente ejemplo estoy limitando a la cuenta de Windows FileZilla a 5 GB y además estoy diciendo que cuando llegue a los 4 GB lance un warning (que podremos ver en el visor de sucesos de Windows).

clip_image006

clip_image008

Un saludo!

martes, 28 de febrero de 2012

Basado en hechos reales (II): Como proteger tu RDP en Internet

Estamos que lo tiramos con los ataques informáticos. Si ya en un anterior post vimos como proteger nuestra instalación de SQL Server en Internet, ahora toca el turno de prevenir ataques contra RDP (Remote Desktop Protocol).

Como ya estábamos sobre aviso y con la mosca detrás de la oreja, ahora estamos todo el día viendo el visor de sucesos de Windows. Pues bien, cual es nuestra sorpresa cuando vemos que tenemos cientos de sucesos de error de auditoria con el identificador de evento 4625. Esto es un error al intentar iniciar sesión en el servidor.

Además resulta que los nombres de cuenta son del todo variopintos, por ejemplo: Administrador, administrator, John… ¿John? Un momento, no conozco a nadie llamado John, así que está claro, algún desaprensivo está utilizando un escaneador de puertos y un generador de nombres de usuario y claves aleatorias para intentar acceder a mi servidor por la fuerza bruta.

Está claro que capar RDP no es una opción porque entonces ni yo mismo podría acceder a mi servidor, pero gracias al equipo de soporte de domitienda y sus recomendaciones, vamos a realizar los siguientes ajustes para intentar poner difícil el acceso a nuestro servidor a nuestros amiguitos los crackers (no los llamo hackers porque hace tiempo aprendí la diferencia entre crackers y hackers):

  • Denegar el permiso para iniciar sesión de forma remota al usuario Administrador.
  • Crear un nuevo usuario en el grupo Administradores, pero con un nombre no común y concederle permiso para iniciar sesión de forma remota.
  • Cambiar el puerto para RDP (un clásico ya lo de cambiar puertos y así frustrar el escaneo de puertos con herramientas automatizadas).

Crear un nuevo usuario en el grupo Administradores y concederle permiso para iniciar sesión de forma remota es muy sencillo puesto que de forma predeterminada, todos los usuarios del grupo Administradores tienen ese permiso concedido (además también del grupo “Usuarios de escritorio remoto”).

Sin embargo, no es tan obvio como denegar el acceso por escritorio remoto al usuario Administrador. Los pasos a seguir son los siguientes:

  • Inicio > Panel de control > Herramientas administrativas > Directivas de seguridad local
  • Directivas locales > Asignación de derechos de usuario
  • Denegar inicio de sesión a través de Servicios de Escritorio remoto
  • Agregar al usuario Administrador

clip_image001

Después de este cambio, si intentamos acceder por RDP con el usuario Administrador obtendremos el siguiente error:

clip_image003

Por último, para cambiar el puerto del escritorio remoto hay que cambiar un valor en el registro de Windows:

En el siguiente enlace de Microsoft lo explica, pero te lo resumo aquí porque es muy sencillo, simplemente hay que cambiar un valor del registro:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TerminalServer\WinStations\RDP-Tcp\PortNumber y donde pone 3389 ponemos cualquier otro.

Lógicamente sólo quedará crear una regla en el firewall para el nuevo puerto seleccionado, y si además en este regla restringimos el acceso a sólo ciertas IPs, pues yo mi parte quedaré muy satisfecho con la seguridad en lo relativo a RDP.

Un saludo!

lunes, 27 de febrero de 2012

Basado en hechos reales: Cómo proteger tu SQL Server en Internet.

Recientemente hemos sufrido un ataque informático contra nuestra base de datos SQL Server 2008 R2 expuesta en Internet.

Por casualidad nos pusimos a mirar el log de errores y descubrimos que había más de 2 millones y medio de intentos de inicio de sesión erróneos del usuario sa desde hace más de 2 meses.

El cómo no nos dimos cuenta en todo ese tiempo de que estábamos siendo atacados por fuerza bruta contra el usuario sa daría para otro post, pero ahora lo más importante es intentar incrementar drásticamente la seguridad de nuestro servidor SQL Server en Internet y dejar de buscar culpables… sobre todo porque yo mismo soy el principal culpable!

“Cabe resaltar que este post asume un escenario con un servidor VPS donde se tienen plenos derechos de administración accediendo por escritorio remoto, es decir, no estoy hablando de Windows Azure ni de un hosting compartido”

La medida más efectiva para evitar ataques contra tu servidor SQL Server en Internet es NO exponerlo a Internet. Esto parece de perogrullo, pero a veces no es tan obvio. ¿Por qué expones tu servidor SQL Server en Internet? Normalmente será porque los desarrolladores argumentan que necesitan acceder a la base de datos para consultar datos, realizar cambios, etc. Yo creo que los desarrolladores (entre los que yo me incluyo) deberíamos pensar que un sistema en producción no es nuestra banco de pruebas personal y que si realmente necesitamos acceder a los datos del servidor hay otras herramientas para darnos acceso sin comprometer la seguridad del servidor. Por ejemplo, escritorio remoto, una VPN, etc.

La verdad es que para un sistema SQL Server en producción no se me ocurre ninguna buena razón para exponerlo directamente en Internet.

Antes de ver que medidas tomamos para garantizar la seguridad de nuestro servidor, la pregunta es ¿Cómo me doy cuenta de que estoy siendo atacado? Pues mirando el log de errores de SQL Server. El log de errores de SQL Server lo podemos consultar de 2 formas distintas:

  • De forma manual, accediendo a Administración > Registros de SQL Server
  • A través de T-SQL, con el comando xp_ReadErrorLog

En cualquiera de los 2 casos podrás ver una información como la siguiente (aquí optamos por el comando xp_ReadErrorLog que es más directo)

xp_ReadErrorLog 0, 1, 'failed'

LogDate

ProcessInfo

Text

2012-02-23 10:39:25.710

NULL

Login failed for user 'sa'. Motivo: la contraseña no es válida para el inicio de sesión proporcionado. [CLIENTE: <local machine>]

Ahora imagina este registro pero multiplicado por 2.5 millones de veces con distintas IP y desde hace 2 meses…y 2.5 GB de log de regalo… ¡terror!

Lo primero es cortar de raíz el acceso al servidor SQL Server desde Internet, y esto es tan sencillo cómo deshabilitar las siguientes reglas del firewall (puede que en tu equipo no se llamen igual, pero básicamente responderán al mismo propósito):

  • MS SQL over TCP protocol. Puerto 1433 sobre TCP.
  • MS SQL Probe. Puerto 1434 sobre UDP.

En este momento ya estamos seguros porque NO exponemos nuestro servidor SQL Server a Internet, pero ¿Qué pasa si tuviéramos necesidad de hacerlo por algún motivo que ahora mismo se me escapa?

Pues te cuento que según mis investigaciones en google y con la ayuda del sentido común (el menos común de los sentidos por otra parte) las medidas que vamos a tomar son las siguientes:

1. Asegurarnos que todas las claves de los login de SQL Server responden a una política de contraseñas fuerte que impiden poner nombres como “Pepe” o “God”. Esto es porque así los diccionarios de ataques por fuerza bruta no encontrarán nuestra contraseña a la primera de turno y además, cuando empiecen a generar contraseñas de forma aleatoria, les cueste más… Gracias a esto después de 2 meses, nuestro servidor no ha sido hackeado (esta es la única triste medalla que podemos lucir a día de hoy).

A este respecto podemos instruir a SQL Server para garantizar una política de contraseñas e incluso forzar la expiración de los login de SQL Server. Más info aquí. Yo por mi parte no voy a activar esta configuración pero prometo que mi contraseña será de muuuchos caracteres, con números, mayúsculas y minúsculas y algún carácter especial como la arroba o el ampersand.

2. Cambiar el puerto por defecto donde escucha SQL Server. Por defecto, SQL Server atiende al puerto 1433 (puerto bien conocido o como dicen en inglés, well known port). Esto significa que cualquier escáner de puertos que detecte que esté abierto el puerto 1433 automáticamente nos dirá que tenemos a SQL Server a la escucha, luego seremos una victima pidiendo a voces un verdugo.

Una lista completa de estos puertos “bien conocidos” la puedes encontrar en la Wikipedia, que por ejemplo nos dice que los siguientes puertos son “bien conocidos”:

  • 1433/tcp Microsoft-SQL-Server
  • 1434/tcp Microsoft-SQL-Monitor
  • 1434/udp Microsoft-SQL-Monitor

Primero vamos a hacer un ejemplo con los puertos abiertos para ver que información nos da un escáner de puertos on-line como http://www.internautas.org/w-scanonline.php

Con los puertos abiertos (sin haber desactivado las reglas del firewall):

clip_image001

Con los puertos cerrados (habiendo desactivado las reglas del firewall):

clip_image002

Aunque en este post nos estamos centrando en los puertos 1433 TCP (puerto por defecto para SQL Server) y 1434 UDP (puerto por defecto para el servicio SQL Server Browser), hay otros puertos menos habituales pero también relacionados con SQL Server que también podrían estar abiertos. Puedes encontrar más información sobre estos puertos aquí.

Además y aunque no es el propósito de este post, si utilizamos instancias con nombre podríamos estar utilizando puertos dinámicos. Un excelente post que habla sobre ello y lo deja muy claro SQL Server runs on which port? Y otro post más que indica como averiguar que puerto concreto está utilizando SQL Server si estamos utilizando los puertos dinámicos How to Find the Dynamic Port reserved by SQL Server?

En cualquier caso, nosotros no tenemos instancias con nombre y la instancia por defecto utiliza el puerto 1433, así que lo cambiaremos a cualquier otro puerto en el rango 49152-65535 (este es el rango para puertos personalizados que seguro no nos darán problemas mañana con la instalación de otros programas). Imaginemos que lo cambio por ejemplo al puerto 50215. El procedimiento para cambiar el puerto está muy bien explicado aquí.

Después de esto ya sólo nos queda crear una regla en el firewall de Windows, en la que incluso podríamos acotar que direcciones IP podrán conectar (yo por mi parte sólo daré acceso a la IP pública de mi oficina).

3. Deshabilitar la cuenta del usuario sa. Esta medida responde a que es por todos conocidos que cualquier instalación de SQL Server tendrá un usuario sa con privilegios administrativos sobre el servidor. Pero claro está que si deshabilitamos esta cuenta, nuestro atacante no sabrá con que login de SQL Server llevar a cabo su ataque. Lógicamente, antes de deshabilitar la cuenta sa estate seguro de que has creado otro login con los mismos privilegios que el usuario sa o que tienes agregada alguna cuenta de usuario de Windows al role de servidor sysadmin.

Hasta aquí hemos llegado con el propósito de intentar poner las cosas un poco más difíciles a nuestros queridos “amigos de lo ajeno”.

Un saludo!

miércoles, 22 de febrero de 2012

Exportar un DataTable a Excel en ASP.NET

Trabajando en una aplicación web, muchas veces tendremos la necesidad de exportar datos a un fichero de excel para que el usuario los descargue y puedo trabajar con ellos en local.

En este post veremos como exportar un objeto DataTable a excel.

Lo cierto es que el origen de la exportación podría ser cualquiera siempre y cuando durante la generación consigamos generar un fichero .csv que es el que, finalmente, se envía al cliente como un fichero adjunto.

En nuestro ejemplo, llamaremos a un controlador genérico (.ashx) que devolverá un fichero .csv que abrirá sin problemas excel.

El código del controlador es muy sencillo y sólo merece especial atención la llamada al método Utilities.ExportDataTableToExcel.

<%@ WebHandler Language="VB" Class="Export" %>
Imports System
Imports System.Web
Imports System.Data
Imports System.Data.SqlClient
Public Class Export : Implements IHttpHandler
    
    Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
        Dim connectionString As String = "Data Source=ESPIGA\SQL2005; Initial Catalog=MSS; User Id=sa; Password=******"
        Using cnn As New SqlConnection(connectionString)
            Dim selectCommand As String = "SELECT * FROM Customers"
            Dim da As New SqlDataAdapter(selectCommand, cnn)
            Dim ds As New DataSet
            da.Fill(ds)
            Utilities.ExportDataTableToExcel(ds.Tables.Item(0))
        End Using
    End Sub
 
    Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property
End Class


Lo que realmente es interesante es el código del método ExportDataTableToExcel.


    Public Overloads Shared Sub ExportDataTableToExcel(ByVal table As DataTable)
        ExportDataTableToExcel(table, String.Empty, Nothing)
    End Sub
    Public Overloads Shared Sub ExportDataTableToExcel(ByVal table As DataTable, ByVal captions As Dictionary(Of String, String))
        ExportDataTableToExcel(table, String.Empty, captions)
    End Sub
    Public Overloads Shared Sub ExportDataTableToExcel(ByVal table As DataTable, ByVal name As String)
        ExportDataTableToExcel(table, name, Nothing)
    End Sub
    Public Overloads Shared Sub ExportDataTableToExcel(ByVal table As DataTable, ByVal name As String, ByVal captions As Dictionary(Of String, String))
        Dim content As New Text.StringBuilder()
        Dim columnName As String = String.Empty
        For Each column As DataColumn In table.Columns
            If Not captions Is Nothing Then
                If Not captions.TryGetValue(column.ColumnName, columnName) Then
                    columnName = column.ColumnName
                End If
            Else
                columnName = column.ColumnName
            End If
            content.Append(columnName & ";")
        Next
        content.Append(Environment.NewLine)
        Dim value As String
        For Each row As DataRow In table.Rows
            For i As Integer = 0 To table.Columns.Count - 1
                value = String.Empty
                If Not row.IsNull(i) Then
                    value = row(i).ToString().Replace(";", String.Empty)
                End If
                content.Append(value & ";")
            Next
            content.Append(Environment.NewLine)
        Next
        content.Length = content.Length - 1
        Dim context As HttpContext = HttpContext.Current
        With context.Response
            .Clear()
            .ContentType = "text/csv"
            If String.IsNullOrEmpty(name) Then
                name = DateTime.Now.ToString("ddMMyyyyHHmmss")
            End If
            .AppendHeader("Content-Disposition", String.Format("attachment; filename={0}.csv", name))
            .Charset = Encoding.Unicode.WebName
            .ContentEncoding = Encoding.Unicode
            .Write(content.ToString())
            .End()
        End With
    End Sub


Un saludo!

martes, 14 de febrero de 2012

Cambio masivo del valor de un campo con T-SQL

Hoy me ha surgido la necesidad de cambiar el valor de un código de producto en toda las tablas de la base de datos. Es decir, donde antes tenía el código X ahora debería de tener el código Y.

Lógicamente podría ejecutar manualmente una sentencia de actualización por cada tabla de la base de datos donde se encuentra el campo, pero coincidiremos ambos en que esto no parece una solución muy elegante. Además, no tengo la seguridad de saber exactamente en que tablas está mi campo y no quiero dejar el proceso a medias.

Como me parece que esta situación se podría repetir en un futuro, tanto para el código del producto como para cualquier otro código, he resuelto escribir un script que automatice la tarea en la medida de lo posible.

A grandes rasgos, el script que te muestro a continuación lleva a cabo los siguientes pasos:

  • Declarar el nombre de campo que queremos actualizar (aquí cabe mencionar que una base de datos normalizada ayudaría al proceso, es decir, si en una tabla has llamado a tu código de producto “ProductID” y en otra “IDProducto”, pues estamos fastidiados…)
  • Declarar el valor que queremos sustituir y el nuevo valor que queremos utilizar.
  • Recorrer las tablas en las que aparece el nombre de campo y deshabilitar todas sus restricciones (imagina que sino las restricciones del tipo ‘foreign key’ impedirían el proceso de actualización masivo)
  • Ejecutar una consulta de actualización por cada tabla donde aparezca el campo.
  • Volver a habilitar las restricciones previamente deshabilitadas.

Lógicamente el script no es perfecto y requiere cambiar algunos ajuste manuales como el tipo de los parámetros, tanto en la declaración inicial como en la sentencia de actualización con sp_executesql.

Además, el script presupone que las ‘foreign key’ declaradas en tus tablas están todas activadas, porque sino y en el último paso, siempre las activará incondicionalmente con independencia de como estuvieran inicialmente.

A continuación el script y espero te sea útil:

 DECLARE @columnName SYSNAME 
 -- Nombre de campo
 SET @columnName = N'ProductID' 
 DECLARE @OldValue NVARCHAR(20)
 -- Valor antiguo
 SET @OldValue = N'1701291'
 DECLARE @NewValue NVARCHAR(20)
 -- Valor nuevo
 SET @NewValue = N'1701291__MODIFICADO'
	
 DECLARE @tableSchema NVARCHAR(128)
 DECLARE @tableName SYSNAME
    
 DECLARE @noCheckStatement NVARCHAR(4000) 
 DECLARE @checkStatement NVARCHAR(4000) 
 SET @noCheckStatement = N''
 SET @checkStatement = N''
 -- Tablas que contienen el nombre de campo
 DECLARE cTables CURSOR READ_ONLY
 FOR
    SELECT  T.TABLE_SCHEMA ,
            T.TABLE_NAME
    FROM    INFORMATION_SCHEMA.TABLES T
            INNER JOIN INFORMATION_SCHEMA.COLUMNS C 
            ON T.TABLE_SCHEMA = C.TABLE_SCHEMA AND T.TABLE_NAME = C.TABLE_NAME
    WHERE   TABLE_TYPE = N'BASE TABLE'
            AND COLUMN_NAME = @columnName
		
 OPEN cTables
 FETCH NEXT FROM cTables INTO @tableSchema, @tableName
 WHILE ( @@fetch_status <> -1 ) 
    BEGIN
        IF ( @@fetch_status <> -2 ) 
            BEGIN          
		-- Generar sentencias para deshabilitar y habilitar restricciones 
                SET @noCheckStatement = @noCheckStatement + N'ALTER TABLE '
                    + @tableSchema + '.' + @tableName
                    + ' NOCHECK CONSTRAINT ALL;'
                SET @checkStatement = @checkStatement + N'ALTER TABLE '
                    + @tableSchema + '.' + @tableName
                    + ' WITH CHECK CHECK CONSTRAINT ALL;'
            END
        FETCH NEXT FROM cTables INTO @tableSchema, @tableName
    END
 CLOSE cTables
 
 -- Deshabilitar restricciones
 EXECUTE sp_executesql @noCheckStatement
 
 DECLARE @statement NVARCHAR(4000) 
 DECLARE @params NVARCHAR(4000)   
		
 OPEN cTables
 FETCH NEXT FROM cTables INTO @tableSchema, @tableName
 WHILE ( @@fetch_status <> -1 ) 
    BEGIN
        IF ( @@fetch_status <> -2 ) 
            BEGIN          
		-- Generar sentencia de actualización
                SET @statement = N'UPDATE ' + @tableSchema + '.' + @tableName
                    + ' SET ' + @columnName + ' = @NewValue WHERE '
                    + @columnName + ' = @OldValue'
                SET @params = N'@NewValue NVARCHAR(20), @OldValue NVARCHAR(20)'                
                -- Actualizar
                EXECUTE sp_executesql @statement, @params, @NewValue,
                    @OldValue
            END
        FETCH NEXT FROM cTables INTO @tableSchema, @tableName
    END
 CLOSE cTables
 DEALLOCATE cTables
 
 -- Habilitar restricciones
 EXECUTE sp_executesql @checkStatement                     

domingo, 12 de febrero de 2012

Generar una cadena de conexión para Entity Framework

Cómo sabrás, un modelo de Entity Framework utiliza una conexión del tipo EntityConnection que a su vez utiliza una conexión específica de un Data Provider como puede ser SqlConnection.

La cadena de conexión de EntityConnection puede resultar un tanto especial si estamos acostumbrados a las cadenas de conexión de toda la vida. En cualquier caso, una cadena de conexión es agregada a nuestro fichero App.config o web.config automáticamente cuando agregamos el modelo a nuestro proyecto.

Un ejemplo de cadena de conexión sería el siguiente:

<add name="PedidosEntities" connectionString="metadata=res://*/Pedidos.csdl|res://*/Pedidos.ssdl|res://*/Pedidos.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=(local);initial catalog=EF;persist security info=True;user id=sa;password=******;multipleactiveresultsets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />

Como puedes comprobar, una cadena de conexión de EntityConnection se puede dividir en 3 partes principales:



  • metadata. Ruta a los metadatos del modelo EDM (esto es la información ssdl, csdl y msl del modelo)

  • provider. Nombre de un proveedor de datos específico (esto es el nombre del proveedor que utilizaremos para conectarnos a la base de datos)

  • provider connection string. Cadena de conexión específica del proveedor de datos anteriormente señalado.

Si tienes que generar esta cadena de conexión en tiempo de ejecución porque quieres conectarte a un origen de datos distinto o por cualquier otro motivo, lo más sensato es hacer uso de los builders oportunos que nos ayudarán en el proceso.


Como referencia he tomado el código expuesto en este blog http://blogs.msdn.com/b/rickandy/archive/2008/12/09/explicit-connection-string-for-ef.aspx, pero lo he modificado para mis cubrir mis propias necesidades:

Imports System.Data.SqlClient
Imports System.Data.EntityClient
Public Class EFUtils
    Public Shared Function GetEntityConnectionString( _
        ByVal dataSource As String, _
        ByVal initialCatalog As String, _
        ByVal userId As String, _
        ByVal password As String, _
        ByVal applicationName As String, _
        ByVal edmFileName As String) As String
        Dim providerName As String = "System.Data.SqlClient"
        Dim sqlConnectionStringBuilder As New SqlConnectionStringBuilder()
        sqlConnectionStringBuilder.DataSource = dataSource
        sqlConnectionStringBuilder.InitialCatalog = initialCatalog
        sqlConnectionStringBuilder.UserID = userId
        sqlConnectionStringBuilder.Password = password
        sqlConnectionStringBuilder.ApplicationName = applicationName
        sqlConnectionStringBuilder.MultipleActiveResultSets = True
        Dim providerString As String = sqlConnectionStringBuilder.ToString()
        Dim entityConnectionStringBuilder As New EntityConnectionStringBuilder()
        entityConnectionStringBuilder.Provider = providerName
        entityConnectionStringBuilder.ProviderConnectionString = providerString
        If String.IsNullOrEmpty(edmFileName) Then
            entityConnectionStringBuilder.Metadata = "res://*"
        Else
            entityConnectionStringBuilder.Metadata = String.Format(".\{0}.csdl|.\{0}.ssdl|.\{0}.msl", edmFileName)
        End If
        Return entityConnectionStringBuilder.ToString()
    End Function
End Class

Lo más relevante de este código es que utilizaremos el parámetro edmFileName para saber si la la cadena de conexión de EntityConnection asume que los metadatos están disponibles como recursos incrustados en el ensamblado (opción por defecto) o para saber si se ha seleccionado la opción “Copiar en el directorio de salida”


Ahora simplemente utilizaremos nuestra nueva cadena de conexión con el constructor de nuestro contexto de trabajo, que acepta una cadena de conexión:

Dim connectionString = EFUtils.GetEntityConnectionString( _
    "lobezno", "Pedidos", "sa", "******", "ConsoleApplication1", String.Empty)
Using conn As New EntityClient.EntityConnection(connectionString)
End Using
Using ctx As New PedidosEntities(connectionString)
End Using

Un saludo!